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.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-03-29 20:32:15 +02:00
committed by Daan De Meyer
parent 34c82cbe2f
commit ab3a2f375f
19 changed files with 61 additions and 57 deletions
+1 -2
View File
@@ -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;
+2 -3
View File
@@ -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;
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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;
+3 -4
View File
@@ -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;
+1 -1
View File
@@ -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;
+1 -2
View File
@@ -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;
+5
View File
@@ -1090,3 +1090,8 @@ LIBSYSTEMD_260 {
global:
sd_session_get_extra_device_access;
} LIBSYSTEMD_259;
LIBSYSTEMD_261 {
global:
sd_varlink_set_sentinel;
} LIBSYSTEMD_260;
+25
View File
@@ -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;
-25
View File
@@ -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,
-2
View File
@@ -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;
+2 -2
View File
@@ -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;
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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;
+1 -1
View File
@@ -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;
+5
View File
@@ -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);
+6 -6
View File
@@ -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"));
+3 -3
View File
@@ -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;