From aefaeebef396366696d98da58982fbfd0592e2a1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 8 Jan 2024 13:51:44 +0100 Subject: [PATCH 1/6] hostnamed: port to sd_event_set_signal_exit() --- src/hostname/hostnamed.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index d629a07d0f..a5cac8b620 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -1615,21 +1615,15 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - r = sd_event_default(&event); if (r < 0) return log_error_errno(r, "Failed to allocate event loop: %m"); (void) sd_event_set_watchdog(event, true); - r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); + r = sd_event_set_signal_exit(event, true); if (r < 0) - return log_error_errno(r, "Failed to install SIGINT handler: %m"); - - r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to install SIGTERM handler: %m"); + return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m"); r = connect_bus(&context, event, &bus); if (r < 0) From 96520e8b0cad693bd1d34ae13ca88718d0b9718c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 8 Jan 2024 13:57:41 +0100 Subject: [PATCH 2/6] hostnamed: move bus and event loop object into Context object It's a bit weird to keep this separate in particular as the polkit object is already part of the context. --- src/hostname/hostnamed.c | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index a5cac8b620..de0ee98aff 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -75,6 +75,8 @@ typedef struct Context { struct stat etc_os_release_stat; struct stat etc_machine_info_stat; + sd_event *event; + sd_bus *bus; Hashmap *polkit_registry; } Context; @@ -94,6 +96,8 @@ static void context_destroy(Context *c) { context_reset(c, UINT64_MAX); hashmap_free(c->polkit_registry); + sd_event_unref(c->event); + sd_bus_flush_close_unref(c->bus); } static void context_read_etc_hostname(Context *c) { @@ -1559,35 +1563,34 @@ static const BusObjectImplementation manager_object = { .vtables = BUS_VTABLES(hostname_vtable), }; -static int connect_bus(Context *c, sd_event *event, sd_bus **ret) { +static int connect_bus(Context *c) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; assert(c); - assert(event); - assert(ret); + assert(c->event); + assert(!c->bus); - r = sd_bus_default_system(&bus); + r = sd_bus_default_system(&c->bus); if (r < 0) return log_error_errno(r, "Failed to get system bus connection: %m"); - r = bus_add_implementation(bus, &manager_object, c); + r = bus_add_implementation(c->bus, &manager_object, c); if (r < 0) return r; - r = bus_log_control_api_register(bus); + r = bus_log_control_api_register(c->bus); if (r < 0) return r; - r = sd_bus_request_name_async(bus, NULL, "org.freedesktop.hostname1", 0, NULL, NULL); + r = sd_bus_request_name_async(c->bus, NULL, "org.freedesktop.hostname1", 0, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to request name: %m"); - r = sd_bus_attach_event(bus, event, 0); + r = sd_bus_attach_event(c->bus, c->event, 0); if (r < 0) return log_error_errno(r, "Failed to attach bus to event loop: %m"); - *ret = TAKE_PTR(bus); return 0; } @@ -1595,8 +1598,6 @@ static int run(int argc, char *argv[]) { _cleanup_(context_destroy) Context context = { .hostname_source = _HOSTNAME_INVALID, /* appropriate value will be set later */ }; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; log_setup(); @@ -1615,21 +1616,27 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - r = sd_event_default(&event); + r = sd_event_default(&context.event); if (r < 0) return log_error_errno(r, "Failed to allocate event loop: %m"); - (void) sd_event_set_watchdog(event, true); + (void) sd_event_set_watchdog(context.event, true); - r = sd_event_set_signal_exit(event, true); + r = sd_event_set_signal_exit(context.event, true); if (r < 0) return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m"); - r = connect_bus(&context, event, &bus); + r = connect_bus(&context); if (r < 0) return r; - r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL); + r = bus_event_loop_with_idle( + context.event, + context.bus, + "org.freedesktop.hostname1", + DEFAULT_EXIT_USEC, + /* check_idle= */ NULL, + /* userdata= */ NULL); if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); From 5ee5b1659aad07a6b718de2868124d490c0dfb73 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 8 Jan 2024 16:14:44 +0100 Subject: [PATCH 3/6] id128-util: do not expose product UUID when running in a container When we run in a container we should show our own system's info, not the hosts hence suppress this info in that case. This matches the behaviour of most other calls in hostnamed to expose system properties. --- src/libsystemd/sd-id128/id128-util.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libsystemd/sd-id128/id128-util.c b/src/libsystemd/sd-id128/id128-util.c index 69fc1bf07e..5808cea757 100644 --- a/src/libsystemd/sd-id128/id128-util.c +++ b/src/libsystemd/sd-id128/id128-util.c @@ -13,6 +13,7 @@ #include "stdio-util.h" #include "string-util.h" #include "sync-util.h" +#include "virt.h" int id128_from_string_nonzero(const char *s, sd_id128_t *ret) { sd_id128_t t; @@ -223,6 +224,13 @@ int id128_get_product(sd_id128_t *ret) { /* Reads the systems product UUID from DMI or devicetree (where it is located on POWER). This is * particularly relevant in VM environments, where VM managers typically place a VM uuid there. */ + r = detect_container(); + if (r < 0) + return r; + if (r > 0) /* Refuse returning this in containers, as this is not a property of our system then, but + * of the host */ + return -ENOENT; + r = id128_read("/sys/class/dmi/id/product_uuid", ID128_FORMAT_UUID, &uuid); if (r == -ENOENT) r = id128_read("/proc/device-tree/vm,uuid", ID128_FORMAT_UUID, &uuid); From 09c7bead29a6664341ddb8ec06467632cf28e896 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 8 Jan 2024 16:42:00 +0100 Subject: [PATCH 4/6] testsuite-71: reset startlimit counter manually The test cases will call quite a lot of "systemctl stop systemd-hostnamed", hence let's make sure we reset the start limit counter each time, to not make this eventually fail. (At other places we disabled the start limit counter, but here I opted for resetting it manually via 'systemctl reset-failed', to test another facet of the mechanism) --- test/units/testsuite-71.sh | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/units/testsuite-71.sh b/test/units/testsuite-71.sh index da765a9d97..92c0beb32b 100755 --- a/test/units/testsuite-71.sh +++ b/test/units/testsuite-71.sh @@ -61,6 +61,11 @@ get_chassis() ( echo "$CHASSIS" ) +stop_hostnamed() { + systemctl stop systemd-hostnamed.service + systemctl reset-failed systemd-hostnamed # reset trigger limit +} + testcase_chassis() { local i @@ -80,7 +85,7 @@ testcase_chassis() { assert_eq "$(get_chassis)" "$i" done - systemctl stop systemd-hostnamed.service + stop_hostnamed rm -f /etc/machine-info # fallback chassis type @@ -95,7 +100,7 @@ restore_sysfs_dmi() { umount /sys/class/dmi/id rm -rf /run/systemd/system/systemd-hostnamed.service.d systemctl daemon-reload - systemctl stop systemd-hostnamed + stop_hostnamed } testcase_firmware_date() { @@ -120,15 +125,15 @@ EOF echo '1' >/sys/class/dmi/id/uevent echo '09/08/2000' >/sys/class/dmi/id/bios_date - systemctl stop systemd-hostnamed + stop_hostnamed assert_in '2000-09-08' "$(hostnamectl)" echo '2022' >/sys/class/dmi/id/bios_date - systemctl stop systemd-hostnamed + stop_hostnamed assert_not_in 'Firmware Date' "$(hostnamectl)" echo 'garbage' >/sys/class/dmi/id/bios_date - systemctl stop systemd-hostnamed + stop_hostnamed assert_not_in 'Firmware Date' "$(hostnamectl)" } From 0a6598bb38a3ba4d627c105ee29582dcd2c118e3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 8 Jan 2024 15:13:07 +0100 Subject: [PATCH 5/6] hostnamed: add simple Varlink API, too --- src/hostname/hostnamed.c | 144 +++++++++++++++++++---- src/shared/meson.build | 1 + src/shared/varlink-io.systemd.Hostname.c | 36 ++++++ src/shared/varlink-io.systemd.Hostname.h | 6 + test/units/testsuite-71.sh | 8 ++ test/units/testsuite-74.varlinkctl.sh | 4 + units/meson.build | 5 + units/systemd-hostnamed.socket | 19 +++ 8 files changed, 202 insertions(+), 21 deletions(-) create mode 100644 src/shared/varlink-io.systemd.Hostname.c create mode 100644 src/shared/varlink-io.systemd.Hostname.h create mode 100644 units/systemd-hostnamed.socket diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index de0ee98aff..f24c126d5d 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -38,6 +38,7 @@ #include "string-table.h" #include "strv.h" #include "user-util.h" +#include "varlink-io.systemd.Hostname.h" #include "virt.h" #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:") @@ -77,6 +78,7 @@ typedef struct Context { sd_event *event; sd_bus *bus; + VarlinkServer *varlink_server; Hashmap *polkit_registry; } Context; @@ -98,6 +100,7 @@ static void context_destroy(Context *c) { hashmap_free(c->polkit_registry); sd_event_unref(c->event); sd_bus_flush_close_unref(c->bus); + varlink_server_unref(c->varlink_server); } static void context_read_etc_hostname(Context *c) { @@ -1347,34 +1350,19 @@ static int method_get_hardware_serial(sd_bus_message *m, void *userdata, sd_bus_ return sd_bus_send(NULL, reply, NULL); } -static int method_describe(sd_bus_message *m, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *hn = NULL, *dhn = NULL, *in = NULL, *text = NULL, +static int build_describe_response(Context *c, bool privileged, JsonVariant **ret) { + _cleanup_free_ char *hn = NULL, *dhn = NULL, *in = NULL, *chassis = NULL, *vendor = NULL, *model = NULL, *serial = NULL, *firmware_version = NULL, *firmware_vendor = NULL; usec_t firmware_date = USEC_INFINITY, eol = USEC_INFINITY; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; sd_id128_t machine_id, boot_id, product_uuid = SD_ID128_NULL; unsigned local_cid = VMADDR_CID_ANY; - Context *c = ASSERT_PTR(userdata); - bool privileged; struct utsname u; int r; - assert(m); - - r = bus_verify_polkit_async( - m, - "org.freedesktop.hostname1.get-description", - /* details= */ NULL, - &c->polkit_registry, - error); - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - /* We ignore all authentication errors here, since most data is unprivileged, the one exception being - * the product ID which we'll check explicitly. */ - privileged = r > 0; + assert(c); + assert(ret); context_read_etc_hostname(c); context_read_machine_info(c); @@ -1457,10 +1445,40 @@ static int method_describe(sd_bus_message *m, void *userdata, sd_bus_error *erro JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_ID128(product_uuid)), JSON_BUILD_PAIR_CONDITION(sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_NULL), JSON_BUILD_PAIR_CONDITION(local_cid != VMADDR_CID_ANY, "VSockCID", JSON_BUILD_UNSIGNED(local_cid)))); - if (r < 0) return log_error_errno(r, "Failed to build JSON data: %m"); + *ret = TAKE_PTR(v); + return 0; +} + +static int method_describe(sd_bus_message *m, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + Context *c = ASSERT_PTR(userdata); + _cleanup_free_ char *text = NULL; + bool privileged; + int r; + + assert(m); + + r = bus_verify_polkit_async( + m, + "org.freedesktop.hostname1.get-description", + /* details= */ NULL, + &c->polkit_registry, + error); + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + /* We ignore all authentication errors here, since most data is unprivileged, the one exception being + * the product ID which we'll check explicitly. */ + privileged = r > 0; + + r = build_describe_response(c, privileged, &v); + if (r < 0) + return r; + r = json_variant_format(v, 0, &text); if (r < 0) return log_error_errno(r, "Failed to format JSON data: %m"); @@ -1564,7 +1582,6 @@ static const BusObjectImplementation manager_object = { }; static int connect_bus(Context *c) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; assert(c); @@ -1594,6 +1611,87 @@ static int connect_bus(Context *c) { return 0; } +static int vl_method_describe(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch dispatch_table[] = { + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + Context *c = ASSERT_PTR(userdata); + bool privileged; + int r; + + assert(link); + assert(parameters); + + r = varlink_dispatch(link, parameters, dispatch_table, /* userdata= */ NULL); + if (r != 0) + return r; + + r = varlink_verify_polkit_async( + link, + c->bus, + "org.freedesktop.hostname1.get-hardware-serial", + /* details= */ NULL, + /* good_user= */ UID_INVALID, + &c->polkit_registry); + if (r == 0) + return 0; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + /* We ignore all authentication errors here, since most data is unprivileged, the one exception being + * the product ID which we'll check explicitly. */ + privileged = r > 0; + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + r = build_describe_response(c, privileged, &v); + if (r < 0) + return r; + + return varlink_reply(link, v); +} + +static int connect_varlink(Context *c) { + int r; + + assert(c); + assert(c->event); + assert(!c->varlink_server); + + r = varlink_server_new(&c->varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + varlink_server_set_userdata(c->varlink_server, c); + + r = varlink_server_add_interface(c->varlink_server, &vl_interface_io_systemd_Hostname); + if (r < 0) + return log_error_errno(r, "Failed to add Hostname interface to varlink server: %m"); + + r = varlink_server_bind_method_many( + c->varlink_server, + "io.systemd.Hostname.Describe", vl_method_describe); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink method calls: %m"); + + r = varlink_server_attach_event(c->varlink_server, c->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach Varlink server to event loop: %m"); + + r = varlink_server_listen_auto(c->varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to bind to passed Varlink sockets: %m"); + if (r == 0) { + r = varlink_server_listen_address(c->varlink_server, "/run/systemd/io.systemd.Hostname", 0666); + if (r < 0) + return log_error_errno(r, "Failed to bind to Varlink socket: %m"); + } + + return 0; +} + static int run(int argc, char *argv[]) { _cleanup_(context_destroy) Context context = { .hostname_source = _HOSTNAME_INVALID, /* appropriate value will be set later */ @@ -1630,6 +1728,10 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = connect_varlink(&context); + if (r < 0) + return r; + r = bus_event_loop_with_idle( context.event, context.bus, diff --git a/src/shared/meson.build b/src/shared/meson.build index 1b95430f88..69a60b0f45 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -174,6 +174,7 @@ shared_sources = files( 'varlink-idl.c', 'varlink-io.systemd.c', 'varlink-io.systemd.Credentials.c', + 'varlink-io.systemd.Hostname.c', 'varlink-io.systemd.Journal.c', 'varlink-io.systemd.ManagedOOM.c', 'varlink-io.systemd.Network.c', diff --git a/src/shared/varlink-io.systemd.Hostname.c b/src/shared/varlink-io.systemd.Hostname.c new file mode 100644 index 0000000000..b2c5e038ff --- /dev/null +++ b/src/shared/varlink-io.systemd.Hostname.c @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.Credentials.h" + +static VARLINK_DEFINE_METHOD( + Describe, + VARLINK_DEFINE_OUTPUT(Hostname, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(StaticHostname, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(PrettyHostname, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(DefaultHostname, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(HostnameSource, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(IconName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(Chassis, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(Deployment, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(Location, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(KernelName, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(KernelRelease, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(KernelVersion, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(OperatingSystemPrettyName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(OperatingSystemCPEName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(OperatingSystemHomeURL, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(OperatingSystemSupportEnd, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(HardwareVendor, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(HardwareModel, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(HardwareSerial, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(FirmwareVersion, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(FirmwareVendor, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(FirmwareDate, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(MachineID, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(BootID, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(ProductUUID, VARLINK_STRING, VARLINK_NULLABLE)); + +VARLINK_DEFINE_INTERFACE( + io_systemd_Hostname, + "io.systemd.Hostname", + &vl_method_Describe); diff --git a/src/shared/varlink-io.systemd.Hostname.h b/src/shared/varlink-io.systemd.Hostname.h new file mode 100644 index 0000000000..29bb20ef68 --- /dev/null +++ b/src/shared/varlink-io.systemd.Hostname.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_Hostname; diff --git a/test/units/testsuite-71.sh b/test/units/testsuite-71.sh index 92c0beb32b..813a676c92 100755 --- a/test/units/testsuite-71.sh +++ b/test/units/testsuite-71.sh @@ -228,6 +228,14 @@ testcase_nss-myhostname() { (! getent hosts -s myhostname fd00:dead:beef:cafe::1) } +test_varlink() { + A="$(mktemp -u)" + B="$(mktemp -u)" + varlinkctl call /run/systemd/io.systemd.Hostname io.systemd.Hostname.Describe '{}' --json=short > "$A" + hostnamectl --json=short > "$B" + cmp "$A" "$B" +} + run_testcases touch /testok diff --git a/test/units/testsuite-74.varlinkctl.sh b/test/units/testsuite-74.varlinkctl.sh index d97de3f604..7912360315 100755 --- a/test/units/testsuite-74.varlinkctl.sh +++ b/test/units/testsuite-74.varlinkctl.sh @@ -113,3 +113,7 @@ done (! varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord Date: Mon, 8 Jan 2024 16:45:07 +0100 Subject: [PATCH 6/6] hostnamed: always include VSockCID property in describe JSON that way clients can distinguish whether there is no cid or whether hostnamed doesn't support it nicely, by just looking if the prop exists (but is null) or not. This is similar how we already handle all other props in the JSON record. --- src/hostname/hostnamed.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index f24c126d5d..f01d771f9b 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -1444,7 +1444,8 @@ static int build_describe_response(Context *c, bool privileged, JsonVariant **re JSON_BUILD_PAIR_ID128("BootID", boot_id), JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_ID128(product_uuid)), JSON_BUILD_PAIR_CONDITION(sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_NULL), - JSON_BUILD_PAIR_CONDITION(local_cid != VMADDR_CID_ANY, "VSockCID", JSON_BUILD_UNSIGNED(local_cid)))); + JSON_BUILD_PAIR_CONDITION(local_cid != VMADDR_CID_ANY, "VSockCID", JSON_BUILD_UNSIGNED(local_cid)), + JSON_BUILD_PAIR_CONDITION(local_cid == VMADDR_CID_ANY, "VSockCID", JSON_BUILD_NULL))); if (r < 0) return log_error_errno(r, "Failed to build JSON data: %m");