From ab3a2f375f63b6966b6b053c674b71a14d6a5965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 28 Mar 2026 16:54:21 +0100 Subject: [PATCH] sd-varlink: export sd_varlink_set_sentinel I tried to implement a varlink service using sd-varlink, and not being able to use the approach with sentinel is exteremely painful. This is useful internally and likewise externally. --- src/bootctl/bootctl-status.c | 3 +-- src/core/varlink-dynamic-user.c | 5 ++--- src/core/varlink-manager.c | 2 +- src/core/varlink-unit.c | 2 +- src/home/homed-varlink.c | 7 +++---- src/import/importd.c | 2 +- src/journal/journalctl-varlink-server.c | 3 +-- src/libsystemd/libsystemd.sym | 5 +++++ src/libsystemd/sd-varlink/sd-varlink.c | 25 ++++++++++++++++++++++++ src/libsystemd/sd-varlink/varlink-util.c | 25 ------------------------ src/libsystemd/sd-varlink/varlink-util.h | 2 -- src/machine/machined-varlink.c | 4 ++-- src/pcrlock/pcrlock.c | 2 +- src/repart/repart.c | 2 +- src/shared/metrics.c | 4 ++-- src/sysext/sysext.c | 2 +- src/systemd/sd-varlink.h | 5 +++++ src/test/test-varlink.c | 12 ++++++------ src/userdb/userwork.c | 6 +++--- 19 files changed, 61 insertions(+), 57 deletions(-) diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c index 178bffb365..2d694885e1 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -22,7 +22,6 @@ #include "pretty-print.h" #include "string-util.h" #include "tpm2-util.h" -#include "varlink-util.h" static int status_entries( const BootConfig *config, @@ -705,7 +704,7 @@ int vl_method_list_boot_entries(sd_varlink *link, sd_json_variant *parameters, s if (r < 0) return r; - r = varlink_set_sentinel(link, "io.systemd.BootControl.NoSuchBootEntry"); + r = sd_varlink_set_sentinel(link, "io.systemd.BootControl.NoSuchBootEntry"); if (r < 0) return r; diff --git a/src/core/varlink-dynamic-user.c b/src/core/varlink-dynamic-user.c index 3f27a1f891..c7e851d005 100644 --- a/src/core/varlink-dynamic-user.c +++ b/src/core/varlink-dynamic-user.c @@ -10,7 +10,6 @@ #include "uid-classification.h" #include "user-util.h" #include "varlink-dynamic-user.h" -#include "varlink-util.h" typedef struct LookupParameters { const char *user_name; @@ -78,7 +77,7 @@ int vl_method_get_user_record(sd_varlink *link, sd_json_variant *parameters, sd_ if (!streq_ptr(p.service, "io.systemd.DynamicUser")) return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; @@ -182,7 +181,7 @@ int vl_method_get_group_record(sd_varlink *link, sd_json_variant *parameters, sd if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 8082b000aa..0cbe26d5d5 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -334,7 +334,7 @@ int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *par if (r <= 0) return r; - r = varlink_set_sentinel(link, NULL); + r = sd_varlink_set_sentinel(link, NULL); if (r < 0) return r; diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index d222148c77..2a2b75c8a9 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -514,7 +514,7 @@ int vl_method_list_units(sd_varlink *link, sd_json_variant *parameters, sd_varli if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); - r = varlink_set_sentinel(link, VARLINK_ERROR_UNIT_NO_SUCH_UNIT); + r = sd_varlink_set_sentinel(link, VARLINK_ERROR_UNIT_NO_SUCH_UNIT); if (r < 0) return r; diff --git a/src/home/homed-varlink.c b/src/home/homed-varlink.c index fb23dc9cde..1bf4c79569 100644 --- a/src/home/homed-varlink.c +++ b/src/home/homed-varlink.c @@ -14,7 +14,6 @@ #include "user-record.h" #include "user-record-util.h" #include "user-util.h" -#include "varlink-util.h" typedef struct LookupParameters { const char *user_name; @@ -104,7 +103,7 @@ int vl_method_get_user_record(sd_varlink *link, sd_json_variant *parameters, sd_ if (!streq_ptr(p.service, m->userdb_service)) return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; @@ -212,7 +211,7 @@ int vl_method_get_group_record(sd_varlink *link, sd_json_variant *parameters, sd if (!streq_ptr(p.service, m->userdb_service)) return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; @@ -277,7 +276,7 @@ int vl_method_get_memberships(sd_varlink *link, sd_json_variant *parameters, sd_ if (!streq_ptr(p.service, m->userdb_service)) return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; diff --git a/src/import/importd.c b/src/import/importd.c index d3363d446c..0de1b21fa5 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -1837,7 +1837,7 @@ static int vl_method_list_transfers(sd_varlink *link, sd_json_variant *parameter if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.Import.NoTransfers"); + r = sd_varlink_set_sentinel(link, "io.systemd.Import.NoTransfers"); if (r < 0) return r; diff --git a/src/journal/journalctl-varlink-server.c b/src/journal/journalctl-varlink-server.c index f44e2a807c..85b4e225f5 100644 --- a/src/journal/journalctl-varlink-server.c +++ b/src/journal/journalctl-varlink-server.c @@ -14,7 +14,6 @@ #include "strv.h" #include "unit-name.h" /* IWYU pragma: keep */ #include "user-util.h" -#include "varlink-util.h" typedef struct GetEntriesParameters { char **units; @@ -100,7 +99,7 @@ int vl_method_get_entries(sd_varlink *link, sd_json_variant *parameters, sd_varl if (r < 0) return r; - r = varlink_set_sentinel(link, "io.systemd.JournalAccess.NoEntries"); + r = sd_varlink_set_sentinel(link, "io.systemd.JournalAccess.NoEntries"); if (r < 0) return r; diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index aa270a483a..e90a9460e2 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -1090,3 +1090,8 @@ LIBSYSTEMD_260 { global: sd_session_get_extra_device_access; } LIBSYSTEMD_259; + +LIBSYSTEMD_261 { +global: + sd_varlink_set_sentinel; +} LIBSYSTEMD_260; diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index c1ffaedfc8..9938ca7063 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -2945,6 +2945,31 @@ _public_ void* sd_varlink_get_userdata(sd_varlink *v) { return v->userdata; } +_public_ int sd_varlink_set_sentinel(sd_varlink *v, const char *error_id) { + assert_return(v, -EINVAL); + + /* If the caller doesn't want a reply, then don't set a sentinel. */ + if (v->state == VARLINK_PROCESSING_METHOD_ONEWAY) + return 0; + + /* This has to be called during a callback, and not after it has exited. */ + assert_return(IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE), + -EUCLEAN); + + char *s = NULL; + if (error_id) { + s = strdup(error_id); + if (!s) + return log_oom_debug(); + } + + if (v->sentinel != POINTER_MAX) + free(v->sentinel); + + v->sentinel = s ?: POINTER_MAX; + return 0; +} + static int varlink_acquire_ucred(sd_varlink *v) { int r; diff --git a/src/libsystemd/sd-varlink/varlink-util.c b/src/libsystemd/sd-varlink/varlink-util.c index 916ac2ba99..83921c57e0 100644 --- a/src/libsystemd/sd-varlink/varlink-util.c +++ b/src/libsystemd/sd-varlink/varlink-util.c @@ -205,31 +205,6 @@ int varlink_check_privileged_peer(sd_varlink *vl) { return 0; } -int varlink_set_sentinel(sd_varlink *v, const char *error_id) { - _cleanup_free_ char *s = NULL; - - assert(v); - - /* If the caller doesn't want a reply, then don't set a sentinel. */ - if (v->state == VARLINK_PROCESSING_METHOD_ONEWAY) - return 0; - - /* This has to be called during a callback, and not after it has exited. */ - assert(IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE)); - - if (error_id) { - s = strdup(error_id); - if (!s) - return -ENOMEM; - } - - if (v->sentinel != POINTER_MAX) - free(v->sentinel); - - v->sentinel = s ? TAKE_PTR(s) : POINTER_MAX; - return 0; -} - DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( varlink_hash_ops, void, diff --git a/src/libsystemd/sd-varlink/varlink-util.h b/src/libsystemd/sd-varlink/varlink-util.h index dee79555ce..ba0f232253 100644 --- a/src/libsystemd/sd-varlink/varlink-util.h +++ b/src/libsystemd/sd-varlink/varlink-util.h @@ -28,6 +28,4 @@ int varlink_server_new( int varlink_check_privileged_peer(sd_varlink *vl); -int varlink_set_sentinel(sd_varlink *v, const char *error_id); - extern const struct hash_ops varlink_hash_ops; diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index fb03ee953f..82b8ed93b3 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -534,7 +534,7 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl if (r != 0) return r; - r = varlink_set_sentinel(link, VARLINK_ERROR_MACHINE_NO_SUCH_MACHINE); + r = sd_varlink_set_sentinel(link, VARLINK_ERROR_MACHINE_NO_SUCH_MACHINE); if (r < 0) return r; @@ -681,7 +681,7 @@ static int vl_method_list_images(sd_varlink *link, sd_json_variant *parameters, if (r != 0) return r; - r = varlink_set_sentinel(link, VARLINK_ERROR_MACHINE_IMAGE_NO_SUCH_IMAGE); + r = sd_varlink_set_sentinel(link, VARLINK_ERROR_MACHINE_IMAGE_NO_SUCH_IMAGE); if (r < 0) return r; diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index c940ab01e8..67303d032b 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -5420,7 +5420,7 @@ static int vl_method_read_event_log(sd_varlink *link, sd_json_variant *parameter return r; // FIXME: We can't use a NULL sentinel here because the output fields in the IDL are non-nullable. - r = varlink_set_sentinel(link, NULL); + r = sd_varlink_set_sentinel(link, NULL); if (r < 0) return r; diff --git a/src/repart/repart.c b/src/repart/repart.c index 5b4d801958..bae376fc0e 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -10427,7 +10427,7 @@ static int vl_method_list_candidate_devices( if (r < 0) return r; - r = varlink_set_sentinel(link, "io.systemd.Repart.NoCandidateDevices"); + r = sd_varlink_set_sentinel(link, "io.systemd.Repart.NoCandidateDevices"); if (r < 0) return r; diff --git a/src/shared/metrics.c b/src/shared/metrics.c index 75a8178958..6c1490cbab 100644 --- a/src/shared/metrics.c +++ b/src/shared/metrics.c @@ -90,7 +90,7 @@ int metrics_method_describe( if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.Metrics.NoSuchMetric"); + r = sd_varlink_set_sentinel(link, "io.systemd.Metrics.NoSuchMetric"); if (r < 0) return r; @@ -127,7 +127,7 @@ int metrics_method_list( if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.Metrics.NoSuchMetric"); + r = sd_varlink_set_sentinel(link, "io.systemd.Metrics.NoSuchMetric"); if (r < 0) return r; diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 157bcb0a0b..20f0ceb3f0 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -2778,7 +2778,7 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl if (r < 0) return r; - r = varlink_set_sentinel(link, "io.systemd.sysext.NoImagesFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.sysext.NoImagesFound"); if (r < 0) return r; diff --git a/src/systemd/sd-varlink.h b/src/systemd/sd-varlink.h index fff6ea8a36..7cda9e7e56 100644 --- a/src/systemd/sd-varlink.h +++ b/src/systemd/sd-varlink.h @@ -205,6 +205,11 @@ int sd_varlink_bind_reply(sd_varlink *v, sd_varlink_reply_t reply); void* sd_varlink_set_userdata(sd_varlink *v, void *userdata); void* sd_varlink_get_userdata(sd_varlink *v); +/* Queue a reply to be sent if no other reply was sent by a method callback. + * Useful when implementing services which send a (possibly empty) series + * of objects and terminate. */ +int sd_varlink_set_sentinel(sd_varlink *v, const char *error_id); + int sd_varlink_get_peer_uid(sd_varlink *v, uid_t *ret); int sd_varlink_get_peer_gid(sd_varlink *v, gid_t *ret); int sd_varlink_get_peer_pid(sd_varlink *v, pid_t *ret); diff --git a/src/test/test-varlink.c b/src/test/test-varlink.c index 186564198c..3324421f68 100644 --- a/src/test/test-varlink.c +++ b/src/test/test-varlink.c @@ -443,7 +443,7 @@ TEST(invalid_parameter) { static int method_with_error_sentinel(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { /* Set an error sentinel and return without sending a reply. The sentinel error should be sent automatically. */ - ASSERT_OK(varlink_set_sentinel(link, "io.test.SentinelError")); + ASSERT_OK(sd_varlink_set_sentinel(link, "io.test.SentinelError")); return 0; } @@ -482,7 +482,7 @@ TEST(sentinel_error) { static int method_with_empty_sentinel(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { /* Set an empty sentinel and return without sending a reply. An empty reply should be sent automatically. */ - ASSERT_OK(varlink_set_sentinel(link, /* error_id= */ NULL)); + ASSERT_OK(sd_varlink_set_sentinel(link, /* error_id= */ NULL)); return 0; } @@ -522,7 +522,7 @@ TEST(sentinel_empty) { static int method_with_sentinel_but_reply(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { /* Set a sentinel but also send a reply. The sentinel should not be used. */ - ASSERT_OK(varlink_set_sentinel(link, "io.test.SentinelError")); + ASSERT_OK(sd_varlink_set_sentinel(link, "io.test.SentinelError")); return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("result", "explicit-reply")); } @@ -561,10 +561,10 @@ TEST(sentinel_with_explicit_reply) { } static int method_with_oneway_sentinel(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - /* The method was called oneway, so varlink_set_sentinel() should be a no-op and the server should + /* The method was called oneway, so sd_varlink_set_sentinel() should be a no-op and the server should * transition back to idle without sending any reply. */ ASSERT_TRUE(FLAGS_SET(flags, SD_VARLINK_METHOD_ONEWAY)); - ASSERT_OK(varlink_set_sentinel(link, "io.test.SentinelError")); + ASSERT_OK(sd_varlink_set_sentinel(link, "io.test.SentinelError")); return 0; } @@ -619,7 +619,7 @@ static int method_with_fd_sentinel(sd_varlink *link, sd_json_variant *parameters /* Set a sentinel so sd_varlink_reply() defers sending: each reply and its pushed fds are captured in * the queue, and the last one is sent as the final reply when the callback returns. */ - ASSERT_OK(varlink_set_sentinel(link, /* error_id= */ NULL)); + ASSERT_OK(sd_varlink_set_sentinel(link, /* error_id= */ NULL)); /* First reply: push one fd with "alpha" content */ ASSERT_OK(fd1 = memfd_new_and_seal_string("data", "alpha")); diff --git a/src/userdb/userwork.c b/src/userdb/userwork.c index 6abb8795e6..aa77cde86b 100644 --- a/src/userdb/userwork.c +++ b/src/userdb/userwork.c @@ -172,7 +172,7 @@ static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *paramete * we are done'; == 0 means 'not processed, caller should process now' */ return r; - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; @@ -313,7 +313,7 @@ static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *paramet if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; @@ -401,7 +401,7 @@ static int vl_method_get_memberships(sd_varlink *link, sd_json_variant *paramete if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r;