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 01/76] 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 480dab4cd2..e744bd8713 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 1fcb88b234147210069469bb6a14f836377d15e1 Mon Sep 17 00:00:00 2001 From: ssahani Date: Fri, 27 Mar 2026 09:19:44 +0530 Subject: [PATCH 02/76] 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 f1049cc7cc..aaf974e312 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 1e159fa310..3ffe1640e7 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 923828b2ea..9a36c312f8 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 914fbccd09..8946f36960 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 2f69c8e712a31f9b99019b602bebe0c7dc82c41b Mon Sep 17 00:00:00 2001 From: ssahani Date: Fri, 27 Mar 2026 09:19:49 +0530 Subject: [PATCH 03/76] 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 4c777ef4e0..554d8da8ef 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 8946f36960..e5f5c07ff1 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 dcc4f0d293..c0c709c32c 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 bab725bd23..38443315e6 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -4972,6 +4972,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): 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 01f6d9e39f94674e5525a16f6c020fd34912db24 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 17 Oct 2025 05:11:09 -0700 Subject: [PATCH 04/76] 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 a89a81c703..57a67af163 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 5fecd3ecec..8d05b5b5ed 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 f921f132254d42b9254cb90e83e49cb867b7d342 Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Sun, 15 Mar 2026 22:22:37 +0000 Subject: [PATCH 05/76] 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 0b7a6b5ed3..1c3abcad23 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 db2ee37f34..e5ee6d3f6d 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 06/76] 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 03404e6cbe..91d97383e7 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 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities): 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 bdbbb9cf911c1a7daad0ef37400c5710110b66a5 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 17 Oct 2025 06:43:36 -0700 Subject: [PATCH 07/76] 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 e388b16339..bdef4d5a94 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 @@ empty: *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 82d1458dd9..fc919dcf36 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 f8f37c480c..24eec3bfcf 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 0385752276..fdfbfc7986 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 08/76] 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 391dc45a6b..24e7a5e366 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 0000000000..8b48da098a --- /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 0000000000..a894e89ad6 --- /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 e8b86845a2..124d6d353b 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 394e7b819b..fbcdce8e06 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 f227dc67d6..61abb754bc 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 f840ff89da..90ff3f6b78 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 782a6dc6e9..2ec1e04089 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 1e3f41e73b035dc5a96281bd243596bc7f0fa6d5 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 17 Oct 2025 05:13:14 -0700 Subject: [PATCH 09/76] 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 24e7a5e366..9a91c37112 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 0000000000..2bb5fa397d --- /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 0000000000..892f8ba65b --- /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 124d6d353b..5f8d1764d5 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 fbcdce8e06..b11aedfa5a 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 61abb754bc..0b3cbf30e5 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 90ff3f6b78..56a50554d8 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 2ec1e04089..318034323b 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 10/76] 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 9a91c37112..c5ef7e48cb 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 0000000000..f4148cafb3 --- /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 0000000000..30fd92781a --- /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 5f8d1764d5..789b21947c 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 b11aedfa5a..422aeb6952 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 0b3cbf30e5..a394071338 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 56a50554d8..2f4bb1c9e9 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 318034323b..9a22757067 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 376d5ccba116673c3f1f469a02b25aca247e9613 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 17 Apr 2026 11:52:41 +0200 Subject: [PATCH 11/76] 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 e33e78d310..2293cd5acb 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 5853d0a53378ed973d8c006531846717ae55090a Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Thu, 16 Apr 2026 15:55:10 +0200 Subject: [PATCH 12/76] 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 6a31f9c4c8..ba7a7fe5f5 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 13/76] 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 61250b7072..cfbea9d981 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 14/76] 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 548a083fac..c970cd4ba6 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 20558ddb5aa37f2e511d787c9f1be1152985865c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 18 Apr 2026 07:19:27 +0900 Subject: [PATCH 15/76] 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 00cbb89a77..21aad9edfc 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 16/76] 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 0000000000..aa0effa4f8 --- /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 ff4592b8b9..bd27cbe2b5 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 50abca4363..9badd9fd67 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 385043dda8..e847d5e2fb 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 4ae933928f..f4878e3c87 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 e33e78d310..894fabf7a6 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 02f89c02ba..bddf7a74bf 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 b819fcd5b8..a9ef81f71d 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 5cad608093..e380550a5a 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 575e2c52ed..a752b1c7cd 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 6764d2e78e..791a041ab8 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 1bd5d94f98..1785ef416f 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 a62f54006e..fb23776b21 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 a6164f2677..78e2d234ab 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 17/76] 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 09dda4508b..bfc6a72183 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 18/76] 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 ec8af57e64..cd2c1a736e 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 19/76] 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 041dc48216..94be969f7f 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 bfc6a72183..042077ab06 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 5034852c6cd0b4957b4c074893c30005c76be73d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 08:10:14 +0900 Subject: [PATCH 20/76] 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 cd2c1a736e..dab734b901 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 21aad9edfc..c8261861a0 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 473d0bd70f..d8b441bfad 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 fb1584a2ea..7bd7e5ab92 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 2937ac569c..da5242c3b7 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 e10605538a..b1d1b0069c 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 68255071e8..bd73be1ea7 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 2251f726b3d9839fcc7cc026ef3b5891ea0bced4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Apr 2026 17:49:31 +0200 Subject: [PATCH 21/76] 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 5f53023c83..558891eaa1 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 31aaa1cace..d7714731c2 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 4476128561..f7ed2a8e41 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 239a0c9273..308ff6a106 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 d3d6583c02..d0daab9dd1 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 7d26541ffa..440c3e5edf 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 22/76] 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 9c469fbe85..76b6507f9a 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 b929485bd5..27268c250b 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 a066396fbc72d06e503acaf38899f949edf16c4c Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 20 Apr 2026 07:32:46 +0000 Subject: [PATCH 23/76] 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 0875390379..2b717e2396 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 878ff5e905..633d54b087 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 1efee1619e..b051bf580c 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 92c7d37da5..b7c70be068 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 b8443fab19..d66eea6987 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 2a0b7d765b..8b1cb3c80f 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 98a2ed9948..d3d7fda33b 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" -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); +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); + +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 0e45584c6d..89c550504d 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 6875834e0f..68ac0e14ee 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 62fd7470cf..118bd71e18 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 5865a39e2f..acd9604f29 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 99c32cc6db..7089d623d1 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 a1f9212e37..a9121ae9f9 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 24/76] 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 4babe1671d..b81334ea06 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 25/76] 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 b81334ea06..4780715e79 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 50987c497d..d34f8ef4c4 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 e2a8569cb3..664ee7cb85 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 26/76] 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 7ad4b470fa..77bf7a8352 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 @@ nothing: 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 bb741278fd..f5c7b9fdec 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 664ee7cb85..00ffa2889c 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 27/76] 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 77bf7a8352..81792b00a8 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 f5c7b9fdec..3c311bc714 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 28/76] 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 4780715e79..044e011f60 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 d34f8ef4c4..cfcb8d1c6f 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 00ffa2889c..8632a603a2 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 29/76] 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 3829313cf9..e319214bf0 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 30/76] 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 a2bbaf1ea8..4d93083923 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 (strchr(QUOTES "\\\x7f", *t)) + if (!FLAGS_SET(flags, STRING_ALLOW_BACKSLASHES) && *t == '\\') + return false; + + 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 820b8688c2..17eaf5d9a6 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 fd910ca76d..bd1bb65282 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 308ff6a106..00ae512668 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 5a7f70d78b..1bc73e7b43 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 52005c8c43..1fae521333 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 b4022105e8..e89cb9da3d 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 01841a97d6..56ee66e5bd 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 63e6599421..b2a656a6fe 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 194e73b491..4ebf47ca9e 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 c78e3cdad9..a195a4b098 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 34ed3e10d3..fa0b830196 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 5c7b007b13..931e669a08 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 4c39b5660a..4c541275c4 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 9668bf0609..14208bc149 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 a6ec5c23d0..fd8b9e95a4 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 eee2daab94..db34916a29 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 c9e966ba04..b0dac0d782 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 b448032939..feaed66cbe 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 84031df784..28ea2e8612 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 02f89c02ba..fa0fa71130 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 cd623bebdb..6e8c17561e 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 5c9a24b119..e5e7b412f5 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)) + 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 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); + "--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 5707569e7e..6e07d72763 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 4a244ae83d..2e44aab963 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 5c70809e47..5c0847b481 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 31/76] 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 34307f3c8b..de51daa24c 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 32/76] 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 11efeb5f04..3bf194f8cf 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 33/76] 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 3bf194f8cf..4fba725bb9 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 34/76] 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 3b407bb070..141387da4f 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()); + 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 parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_USR, - }; - - 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' }, - {} - }; - - 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, "sr: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_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 35/76] 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 | 129 ++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 72 deletions(-) diff --git a/src/import/export.c b/src/import/export.c index 389d5428bf..71f892feb0 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()); + 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(); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_FORMAT, - ARG_CLASS, - ARG_SYSTEM, - ARG_USER, - }; - - 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 }, - {} - }; - - 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 36/76] 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 4b2394d414..3605320300 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()); + 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(); -} - -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 37/76] 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 | 64 ++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/src/growfs/growfs.c b/src/growfs/growfs.c index 8481257ab7..e7a0ca385b 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(); - 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); + 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", + 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 38/76] 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 2c6eed3248..cc48bf22fb 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 39/76] 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 247cc0ec16..e0bbcfe6c2 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 40/76] 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 605839169c..a07634a249 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, - }; + OptionParser state = { argc, argv }; + const char *arg; - 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) + 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 41/76] 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 516527aa2b..dcdd264226 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()); + 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(); -} - -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); - 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_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 42/76] 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 | 126 +++++++++---------- 1 file changed, 59 insertions(+), 67 deletions(-) diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c index 70774dcdd1..4fb7082112 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; - break; - - 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 ARG_COMMIT: + OPTION_LONG("commit", NULL, "Commit transient ID"): arg_commit = true; break; - case ARG_PRINT: - arg_print = true; + 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 '?': - return -EINVAL; + 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; - default: - assert_not_reached(); + 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("print", NULL, "Print used machine ID"): + arg_print = true; + break; } - 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 43/76] 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 | 63 +++++++++++++++------------------ 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/src/modules-load/modules-load.c b/src/modules-load/modules-load.c index e8aeb67f1f..5435de4e2e 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(); - 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); + 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", + 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 44/76] 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 b18e79d622..b40f86fafb 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 45/76] 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 703b9c3f0e..51dfe1f1a4 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 46/76] 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 eb6d917025..de67ddfdc8 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 47/76] 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 97607f9b59..f46a0a0372 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 48/76] 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 397a32b42f..21f58aac15 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 49/76] 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 3fe039a053..4cddfc32d9 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 50/76] 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 5f64304219..1bec605b0c 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 51/76] 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 2b717e2396..287456ad6e 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 7ed12dea95..3dc3cede3e 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 0000000000..54b3027a97 --- /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 77b71fb4f9..c0603638d9 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 d70493fe7a..54473d7210 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 fdd2e1d8e6..462bb7d81f 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 8df2f5235c..c7689073dd 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 a1bac8301b..1445c7cd5f 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 b7158841fa..dd4a73a519 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 2ec63ca887..0000000000 --- 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 222cac16b0..0000000000 --- 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 41fc130ace..0000000000 --- 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 06935f2cd8..0000000000 --- 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 f9ef4c753e..0000000000 --- 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 53ba2e5b5a..0000000000 --- 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 ec9d34637b..0000000000 --- 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 d83dd89e52..0000000000 --- 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 c5ef7e48cb..d24fc3b6d3 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 8b0de88674..0000000000 --- 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 e1ebc3e509..0000000000 --- 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 00361a0017..134416bcc8 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 e5f5c07ff1..81e49c860d 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 e6bcc51add..0000000000 --- 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 6b6ae1558c..881fd911e4 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 3102563617..cceaa9c378 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 5012276c82..d9b7940f63 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 54db791ccf..a0b03f41a6 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 52/76] 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 0000000000..c93e685afd --- /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 54b3027a97..32ab32e93d 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 53/76] 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 0000000000..43b0c59f49 --- /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 0000000000..ec2f81817b --- /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 54/76] 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 703b9c3f0e..51dfe1f1a4 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 55/76] 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 ccb6f86bfe..ddcb863f4a 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 56/76] 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 c1b2948e6c..08bff2cf9e 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 979e07ac80..8386bdb1b1 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 1c1e4f0e24..45584b7a6d 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 23bd1f666e..86ec2d28fd 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 40de137905..ccf0ec6be3 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 79cab18822..34444811ad 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 147a174da0..0f45fad205 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 462bb7d81f..74cb2b3d52 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 c7689073dd..e60e6ca7ef 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 1445c7cd5f..3c73a682a3 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 dd4a73a519..87e7d60c05 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 2138367218..bcc24922ae 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 dea0699ba4..cfd63fe303 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 e89cb9da3d..3bdce441a8 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 46e157af20..967274b950 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 1412ccce8b..95b77e4fbf 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 17905de2c7..d06c9f0293 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 6988d39ca4..a133a27dd2 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 b3fe28e2cf..973a954acd 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 8481257ab7..0d4097be38 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 633d54b087..3bf993ef53 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 a6e7d3751a..578d5cce91 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 e8d7282cbf..1de66b4c03 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 211565880c..8ab3498656 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 948cd82988..5f17084f9f 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 c0e55c3597..b7cfed9bf3 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 cd24dec34e..23bb48f687 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 df1a6bc600..bf62e9082b 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 c0810331a9..a55316c73a 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 6c70b8b1af..4e571e7056 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 3e0df8c86b..93ccddc25b 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 81e49c860d..0801ba977a 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 5cbc58969f..bd28a67e6b 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 438346a545..c7ccfd4996 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 d9b7940f63..11bb2e7d8f 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 5e60ad4694..ac84fb58d1 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 d66eea6987..e307bfe13f 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 e70f8efa34..c64bf86e13 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 eec2819767..094ef00698 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 07206bdb5f..92d920e171 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 1b74101ae4..5e2aeb94a9 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 e24f4315a0..5f01bfae01 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 06d6bf30e2..e87ba84504 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 f1b7ccdfeb..18bf100d06 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 09502eadc4..718a0f15a9 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 c8e2e7ce4d..1d2fdef781 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 ecae890035..b3d14f9b5f 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 72d374c235..95f7256a56 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 ba7910d864..fd5ffd706d 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 2e3ffe4c9e..27e704869f 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 59f9f16ae3..9254b83dd7 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 4ca3faf602..112649f371 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 e33e78d310..043c7060fb 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 727e1d0625..c90da5fc46 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 be9e673a51..97188ca414 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 b5c3db80ee..e9d9e959da 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 8b1cb3c80f..5eaa91160a 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 d3d7fda33b..bdac11c507 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 d29719785f..fd232d51d0 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 a1b4a6c498..01512b95dd 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 c971be9aef..918f8c0a57 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 02b6b36b3c..2ef0b37959 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 db1c31d223..f1f78aad5d 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 bf5791955b..478d3d33b6 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 759b1c7574..8bb05a2fbd 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 df3ba146f9..531684d64e 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 3f79916cbc..5c469b662c 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 18d020d34c..cc00006af9 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 c5d3875a0d..4f88100be7 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 0d63675667..27e98888d0 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 bfbb00cd4a..7eb7b230b2 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 89c550504d..07b504797a 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 8ad7dab180..9bf1a827b0 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 f5eaf35c90..629979722c 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 45ffd9113b..12d3da82e2 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 0f04e97377..9728c3e00d 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 2464e144fb..96d522d8f9 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 33c77b01a0..6ce858c744 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 e813829b63..2ae00aa620 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 3eba9495b6..dd0cdcfa13 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 829025a10d..468734f590 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 22a7635170..3ac76c1f9c 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 8c85c61994..ba8827fb07 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 6598bf8214..79efdc4574 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 96b25c4ac3..eea7fec893 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 51279eabc4..92d850b6e5 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 ee6d3c40f9..d1c639e74c 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 9a624e29e1..667ff8e3ff 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 8f99b67134..9d908ee948 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 49328ccde2..167a0e40de 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 51c2ba6505..7037ef52d1 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 6b8e318a4c..19e0d2b488 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 ab499e8c4f..f73503bc4b 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 2dcd5f2359..e455306be9 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 3f7dc88a6a..21d228c263 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 fa0fa71130..986b2831e1 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 5ada96e8e1..8576f278cf 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 f064473ef7..04efbfeb36 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 0ac35d1b56..0ea1e38d91 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 118bd71e18..0b346da62d 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 acd9604f29..fa5621e88c 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 a9121ae9f9..c2eaac7b53 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 ebd0260892..e14205bdf8 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 2a400f75d0..a8dd397acc 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 69d2d05b76..3e318a36c9 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 899f8f6351..7497596cc5 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 7934aa879d..d6f140e0aa 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 7e41f69299..9975ffca39 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 34755b1f8b..1acbe2f090 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 edadb71c7a..ca40b62d78 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 840febbd42..593eee89b8 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 1106eaf1fe..44a58387f0 100644 --- a/src/validatefs/validatefs.c +++ b/src/validatefs/validatefs.c @@ -291,9 +291,9 @@ static int validate_gpt_metadata_one(sd_device *d, const char *path, const Valid assert(d); assert(f); - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_ERR); if (r < 0) - return log_error_errno(r, "Cannot validate GPT constraints, refusing."); + return r; _cleanup_close_ int block_fd = sd_device_open(d, O_RDONLY|O_CLOEXEC|O_NONBLOCK); if (block_fd < 0) From fcff95831909f0ff9eb8be6ac61c411bc09fa566 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 8 Apr 2026 14:31:00 +0000 Subject: [PATCH 57/76] 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 9cb84bbe1e..d5277ad7ef 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 58/76] 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 5390754661..53f4a1a60c 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 d6258ce2fc..7beb96403d 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 5749136a5d..5c5ec4ccbc 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 b2b3f4f5a8..efa0dae58d 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 d5277ad7ef..4867bf3609 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 307197ac19..e9686786fe 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 fa8c3402ff..b7f03501f7 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 59/76] 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 045aa60db8..54d6f83915 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 b39d3cbd6d..85f3023083 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 ee28fa7475..10dc527236 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 32c14fb14a..4e4abaebf8 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 67a4f5d69c..8dcbf766e6 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 c7ccfd4996..b1c8defb6c 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 @@ finish: 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 e9686786fe..066ee29115 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 2dbfe368a4..cc241beff9 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 bc01250a55..ae062fc58d 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 fa42c5bee8..79d8ee0565 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 b7f03501f7..8ccd631762 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 60/76] 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 1184331254..88ba2c2bc0 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 61/76] 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 6f492bc9ba..03d475e5e7 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 62/76] 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 287456ad6e..95d95c43cd 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 dd00fa737c..fc9ffd58c9 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 b0593e3f1a..1198d2c15c 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 7cb8846224..42e3f6df29 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 4867bf3609..1fbcc27815 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 c8d97526d3..f6aa71349c 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 0000000000..1f17c4f5a1 --- /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 0000000000..a110b437c3 --- /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 07b504797a..1ab14a5c92 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 32751e85e1..6dd55d8a07 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 80142e24f5..488ba7ea6e 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 7089d623d1..9544c166a5 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 c2eaac7b53..d3121981cf 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 63/76] 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 e307bfe13f..e91d32ef0b 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 64/76] 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 822cf26c89..600207e947 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 bca9f74c3a..ab7637e616 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 2650318713..e189cce23a 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 7ddb9f871b..51c2a5fa77 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 f9a588da26..85ec13f128 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 50abca4363..a0c64e8601 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 1ae92bf91b..a8638d2b3a 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 e9bb27c254..46b3a546c9 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 11265c8b41..2d882343d3 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 ff9449abd1..8e5161eba0 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 b36354fb0a..9249f70177 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 6f39736958..b544538d9d 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 3bf993ef53..96ac65a6fb 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 29581a0522..9a373e124b 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 dd2eb60cf6..4f3601e681 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 e91d32ef0b..91cd77b448 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 fd5ffd706d..0d96d82c62 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 27e704869f..4e994ce9f9 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 043c7060fb..c51e7b964e 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 cc9ac80a61..21432d41f1 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 2e44aab963..b5d57fd1fe 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 65/76] 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 b544538d9d..ff6a5909e7 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 578d5cce91..d63d481447 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 91cd77b448..fac2bd817a 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 66/76] 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 0d96d82c62..47a148459d 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 67/76] 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 95d95c43cd..a902bc96aa 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 68/76] 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 1bec605b0c..142d2246c1 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 f59b1f1a7dd2677b71a4ea4a47c951da85fff3a5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 22 Apr 2026 23:43:36 +0200 Subject: [PATCH 69/76] 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 8da35172c5..6b4371aa50 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 1d78c2d327cbd4e738d0f1281a976a771f643517 Mon Sep 17 00:00:00 2001 From: Nandakumar Raghavan Date: Tue, 21 Apr 2026 13:14:17 +0000 Subject: [PATCH 70/76] 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 6716a8d1aa..abbb955e59 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 71/76] 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 91cd77b448..75fb79c48a 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 fb3dbcfad5..7b536fa092 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 72/76] 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 75fb79c48a..b089689e73 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 73/76] 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 5026b10ff4..fb20ba9cd0 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 74/76] 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 b66b5455da..7ad85077b4 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 75/76] 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 0ad3c9c772..83544b3606 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 7cc607c4c5..2e623a2728 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 14c7014d7faa21ae8558982acfa2a45500ba3fb7 Mon Sep 17 00:00:00 2001 From: vlefebvre Date: Wed, 22 Apr 2026 17:36:04 +0200 Subject: [PATCH 76/76] 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 1198d2c15c..5abebc04cf 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