introduce notify_socket_prepare() and use it where applicable (#36911)

This introduces notify_socket_prepare(), which creates an autobind
notify socket and IO event source for the socket. Then, use it where we
send notification messages from worker processes to their manager
process.
This commit is contained in:
Yu Watanabe
2025-04-01 18:20:47 +09:00
committed by GitHub
11 changed files with 218 additions and 214 deletions
+1 -13
View File
@@ -1308,21 +1308,9 @@ static int home_start_work(
if (r < 0)
return r;
if (r == 0) {
_cleanup_free_ char *joined = NULL;
const char *suffix, *unix_path;
/* Child */
suffix = getenv("SYSTEMD_HOME_DEBUG_SUFFIX");
if (suffix) {
joined = strjoin("/run/systemd/home/notify.", suffix);
if (!joined)
return log_oom();
unix_path = joined;
} else
unix_path = "/run/systemd/home/notify";
if (setenv("NOTIFY_SOCKET", unix_path, 1) < 0) {
if (setenv("NOTIFY_SOCKET", h->manager->notify_socket_path, /* overwrite = */ true) < 0) {
log_error_errno(errno, "Failed to set $NOTIFY_SOCKET: %m");
_exit(EXIT_FAILURE);
}
+12 -53
View File
@@ -291,7 +291,7 @@ Manager* manager_free(Manager *m) {
m->device_monitor = sd_device_monitor_unref(m->device_monitor);
m->inotify_event_source = sd_event_source_unref(m->inotify_event_source);
m->notify_socket_event_source = sd_event_source_unref(m->notify_socket_event_source);
m->notify_socket_path = mfree(m->notify_socket_path);
m->deferred_rescan_event_source = sd_event_source_unref(m->deferred_rescan_event_source);
m->deferred_gc_event_source = sd_event_source_unref(m->deferred_gc_event_source);
m->deferred_auto_login_event_source = sd_event_source_unref(m->deferred_auto_login_event_source);
@@ -1142,64 +1142,23 @@ static int on_notify_socket(sd_event_source *s, int fd, uint32_t revents, void *
}
static int manager_listen_notify(Manager *m) {
_cleanup_close_ int fd = -EBADF;
union sockaddr_union sa = {
.un.sun_family = AF_UNIX,
.un.sun_path = "/run/systemd/home/notify",
};
const char *suffix;
int r;
assert(m);
assert(!m->notify_socket_event_source);
assert(!m->notify_socket_path);
suffix = getenv("SYSTEMD_HOME_DEBUG_SUFFIX");
if (suffix) {
_cleanup_free_ char *unix_path = NULL;
unix_path = strjoin("/run/systemd/home/notify.", suffix);
if (!unix_path)
return log_oom();
r = sockaddr_un_set_path(&sa.un, unix_path);
if (r < 0)
return log_error_errno(r, "Socket path %s does not fit in sockaddr_un: %m", unix_path);
}
fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (fd < 0)
return log_error_errno(errno, "Failed to create listening socket: %m");
(void) mkdir_parents(sa.un.sun_path, 0755);
(void) sockaddr_un_unlink(&sa.un);
if (bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0)
return log_error_errno(errno, "Failed to bind to socket: %m");
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true);
r = notify_socket_prepare(
m->event,
SD_EVENT_PRIORITY_NORMAL - 5, /* Make sure we process sd_notify() before SIGCHLD for
* any worker, so that we always know the error number
* of a client before it exits. */
on_notify_socket,
m,
&m->notify_socket_path);
if (r < 0)
return log_error_errno(r, "Failed to enable SO_PASSCRED on notify socket: %m");
return log_error_errno(r, "Failed to prepare notify socket: %m");
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSPIDFD, true);
if (r < 0)
log_warning_errno(r, "Failed to enable SO_PASSPIDFD on notify socket, ignoring: %m");
r = sd_event_add_io(m->event, &m->notify_socket_event_source, fd, EPOLLIN, on_notify_socket, m);
if (r < 0)
return log_error_errno(r, "Failed to allocate event source for notify socket: %m");
(void) sd_event_source_set_description(m->notify_socket_event_source, "notify-socket");
/* Make sure we process sd_notify() before SIGCHLD for any worker, so that we always know the error
* number of a client before it exits. */
r = sd_event_source_set_priority(m->notify_socket_event_source, SD_EVENT_PRIORITY_NORMAL - 5);
if (r < 0)
return log_error_errno(r, "Failed to alter priority of NOTIFY_SOCKET event source: %m");
r = sd_event_source_set_io_fd_own(m->notify_socket_event_source, true);
if (r < 0)
return log_error_errno(r, "Failed to pass ownership of notify socket: %m");
return TAKE_FD(fd);
return 0;
}
static int manager_add_device(Manager *m, sd_device *d) {
+1 -2
View File
@@ -42,8 +42,7 @@ struct Manager {
sd_event_source *inotify_event_source;
/* An event source we receive sd_notify() messages from our worker from */
sd_event_source *notify_socket_event_source;
char *notify_socket_path;
sd_device_monitor *device_monitor;
+9 -30
View File
@@ -107,9 +107,7 @@ struct Manager {
Hashmap *polkit_registry;
int notify_fd;
sd_event_source *notify_event_source;
char *notify_socket_path;
bool use_btrfs_subvol;
bool use_btrfs_quota;
@@ -467,7 +465,7 @@ static int transfer_start(Transfer *t) {
/* Child */
if (setenv("SYSTEMD_LOG_TARGET", "console-prefixed", 1) < 0 ||
setenv("NOTIFY_SOCKET", "/run/systemd/import/notify", 1) < 0) {
setenv("NOTIFY_SOCKET", t->manager->notify_socket_path, 1) < 0) {
log_error_errno(errno, "setenv() failed: %m");
_exit(EXIT_FAILURE);
}
@@ -624,8 +622,7 @@ static Manager *manager_unref(Manager *m) {
if (!m)
return NULL;
sd_event_source_unref(m->notify_event_source);
safe_close(m->notify_fd);
free(m->notify_socket_path);
while ((t = hashmap_first(m->transfers)))
transfer_unref(t);
@@ -687,10 +684,6 @@ static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void
static int manager_new(Manager **ret) {
_cleanup_(manager_unrefp) Manager *m = NULL;
static const union sockaddr_union sa = {
.un.sun_family = AF_UNIX,
.un.sun_path = "/run/systemd/import/notify",
};
int r;
assert(ret);
@@ -725,26 +718,12 @@ static int manager_new(Manager **ret) {
if (r < 0)
log_debug_errno(r, "Failed to enable watchdog logic, ignoring: %m");
m->notify_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (m->notify_fd < 0)
return -errno;
(void) mkdir_parents_label(sa.un.sun_path, 0755);
(void) sockaddr_un_unlink(&sa.un);
if (bind(m->notify_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0)
return -errno;
r = setsockopt_int(m->notify_fd, SOL_SOCKET, SO_PASSCRED, true);
if (r < 0)
return r;
r = setsockopt_int(m->notify_fd, SOL_SOCKET, SO_PASSPIDFD, true);
if (r < 0)
log_debug_errno(r, "Failed to enable SO_PASSPIDFD on notification socket, ignoring. %m");
r = sd_event_add_io(m->event, &m->notify_event_source,
m->notify_fd, EPOLLIN, manager_on_notify, m);
r = notify_socket_prepare(
m->event,
SD_EVENT_PRIORITY_NORMAL,
manager_on_notify,
m,
&m->notify_socket_path);
if (r < 0)
return r;
+17 -38
View File
@@ -494,28 +494,28 @@ static int action_fork(char *const *_command) {
if (!command)
return log_oom();
_cleanup_close_ int socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (socket_fd < 0)
return log_error_errno(errno, "Failed to allocate AF_UNIX socket for notifications: %m");
r = setsockopt_int(socket_fd, SOL_SOCKET, SO_PASSCRED, true);
if (r < 0)
return log_error_errno(r, "Failed to enable SO_PASSCRED: %m");
r = setsockopt_int(socket_fd, SOL_SOCKET, SO_PASSPIDFD, true);
if (r < 0)
log_debug_errno(r, "Failed to enable SO_PASSPIDFD, ignoring: %m");
_cleanup_free_ char *addr_string = NULL;
r = socket_autobind(socket_fd, &addr_string);
if (r < 0)
return log_error_errno(r, "Failed to bind notify socket: %m");
_cleanup_free_ char *c = strv_join(command, " ");
if (!c)
return log_oom();
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
r = sd_event_new(&event);
if (r < 0)
return log_error_errno(r, "Failed to allocate event loop: %m");
_cleanup_(pidref_done) PidRef child = PIDREF_NULL;
_cleanup_free_ char *addr_string = NULL;
r = notify_socket_prepare(
event,
SD_EVENT_PRIORITY_NORMAL - 10, /* If we receive both the sd_notify() message and a
* SIGCHLD always process sd_notify() first, it's the
* more interesting, "positive" information. */
on_notify_socket,
&child,
&addr_string);
if (r < 0)
return log_error_errno(r, "Failed to prepare notify socket: %m");
r = pidref_safe_fork_full(
"(notify)",
/* stdio_fds= */ (const int[]) { -EBADF, -EBADF, STDERR_FILENO },
@@ -526,11 +526,6 @@ static int action_fork(char *const *_command) {
if (r < 0)
return log_error_errno(r, "Failed to fork child in order to execute '%s': %m", c);
if (r == 0) {
/* Let's explicitly close the fds we just opened. Not because it was necessary (we should be
* setting O_CLOEXEC after all on all of them), but mostly to make debugging nice */
socket_fd = safe_close(socket_fd);
pidref_done(&child);
if (setenv("NOTIFY_SOCKET", addr_string, /* overwrite= */ true) < 0) {
log_error_errno(errno, "Failed to set $NOTIFY_SOCKET: %m");
_exit(EXIT_MEMORY);
@@ -549,22 +544,6 @@ static int action_fork(char *const *_command) {
BLOCK_SIGNALS(SIGCHLD);
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
r = sd_event_new(&event);
if (r < 0)
return log_error_errno(r, "Failed to allocate event loop: %m");
_cleanup_(sd_event_source_disable_unrefp) sd_event_source *socket_event_source = NULL;
r = sd_event_add_io(event, &socket_event_source, socket_fd, EPOLLIN, on_notify_socket, &child);
if (r < 0)
return log_error_errno(r, "Failed to allocate IO source: %m");
/* If we receive both the sd_notify() message and a SIGCHLD always process sd_notify() first, it's
* the more interesting, "positive" information. */
r = sd_event_source_set_priority(socket_event_source, SD_EVENT_PRIORITY_NORMAL - 10);
if (r < 0)
return log_error_errno(r, "Failed to change child event source priority: %m");
_cleanup_(sd_event_source_disable_unrefp) sd_event_source *child_event_source = NULL;
r = event_add_child_pidref(event, &child_event_source, &child, WEXITED, on_child, /* userdata= */ NULL);
if (r < 0)
+55
View File
@@ -7,6 +7,61 @@
#include "strv.h"
#include "user-util.h"
int notify_socket_prepare(
sd_event *event,
int64_t priority,
sd_event_io_handler_t handler,
void *userdata,
char **ret_path) {
int r;
assert(event);
_cleanup_close_ int fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (fd < 0)
return log_debug_errno(errno, "Failed to create notification socket: %m");
_cleanup_free_ char *path = NULL;
r = socket_autobind(fd, &path);
if (r < 0)
return log_debug_errno(r, "Failed to bind notification socket: %m");
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true);
if (r < 0)
return log_debug_errno(r, "Failed to enable SO_PASSCRED on notification socket: %m");
/* SO_PASSPIDFD is supported since kernel v6.5. */
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSPIDFD, true);
if (r < 0)
log_debug_errno(r, "Failed to enable SO_PASSPIDFD on notification socket, ignoring. %m");
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
r = sd_event_add_io(event, &s, fd, EPOLLIN, handler, userdata);
if (r < 0)
return log_debug_errno(r, "Failed to create notification event source: %m");
r = sd_event_source_set_priority(s, priority);
if (r < 0)
return log_debug_errno(r, "Failed to set priority to notification event source: %m");
r = sd_event_source_set_io_fd_own(s, true);
if (r < 0)
return log_debug_errno(r, "Failed to make notification event source own file descriptor: %m");
TAKE_FD(fd);
(void) sd_event_source_set_description(s, "notify-socket");
r = sd_event_source_set_floating(s, true);
if (r < 0)
return log_debug_errno(r, "Failed to make notification event source floating: %m");
if (ret_path)
*ret_path = TAKE_PTR(path);
return 0;
}
int notify_recv_with_fds(
int fd,
char **ret_text,
+9
View File
@@ -3,9 +3,18 @@
#include <sys/socket.h>
#include "sd-event.h"
#include "fdset.h"
#include "pidref.h"
int notify_socket_prepare(
sd_event *event,
int64_t priority,
sd_event_io_handler_t handler,
void *userdata,
char **ret_path);
int notify_recv_with_fds(
int fd,
char **ret_text,
+13 -44
View File
@@ -945,15 +945,13 @@ static int callout_context_new(const Transfer *t, const Instance *i, TransferPro
}
static int helper_on_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
_cleanup_(callout_context_freep) CalloutContext *ctx = ASSERT_PTR(userdata);
CalloutContext *ctx = ASSERT_PTR(userdata);
int r;
assert(s);
assert(si);
assert(ctx);
pidref_done(&ctx->pid);
if (si->si_code == CLD_EXITED) {
if (si->si_status == EXIT_SUCCESS) {
r = 0;
@@ -1031,11 +1029,7 @@ static int run_callout(
const Instance *instance,
TransferProgress callback,
void *userdata) {
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
_cleanup_(sd_event_source_unrefp) sd_event_source *exit_source = NULL, *notify_source = NULL;
_cleanup_close_ int fd = -EBADF;
_cleanup_free_ char *bind_name = NULL;
union sockaddr_union bsa;
int r;
assert(name);
@@ -1043,11 +1037,11 @@ static int run_callout(
assert(cmdline[0]);
_cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
r = callout_context_new(transfer, instance, callback, name, userdata, &ctx);
if (r < 0)
return log_oom();
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
r = sd_event_new(&event);
if (r < 0)
return log_error_errno(r, "Failed to create event: %m");
@@ -1060,27 +1054,15 @@ static int run_callout(
if (r < 0)
return log_error_errno(r, "Failed to register signal to event: %m");
fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (fd < 0)
return log_error_errno(errno, "Failed to create UNIX socket for notification: %m");
if (asprintf(&bind_name, "@%" PRIx64 "/sysupdate/" PID_FMT "/notify", random_u64(), getpid_cached()) < 0)
return log_oom();
r = sockaddr_un_set_path(&bsa.un, bind_name);
_cleanup_free_ char *bind_name = NULL;
r = notify_socket_prepare(
event,
SD_EVENT_PRIORITY_NORMAL - 5,
helper_on_notify,
ctx,
&bind_name);
if (r < 0)
return log_error_errno(r, "Failed to set socket path: %m");
if (bind(fd, &bsa.sa, r) < 0)
return log_error_errno(errno, "Failed to bind to notification socket: %m");
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true);
if (r < 0)
return log_error_errno(r, "Failed to enable SO_PASSCRED: %m");
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSPIDFD, true);
if (r < 0)
log_debug_errno(r, "Failed to enable SO_PASSPIDFD, ignoring: %m");
return log_error_errno(r, "Failed to prepare notify socket: %m");
r = pidref_safe_fork(ctx->name, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &ctx->pid);
if (r < 0)
@@ -1097,7 +1079,8 @@ static int run_callout(
}
/* Quit the loop w/ when child process exits */
r = event_add_child_pidref(event, &exit_source, &ctx->pid, WEXITED, helper_on_exit, (void*) ctx);
_cleanup_(sd_event_source_unrefp) sd_event_source *exit_source = NULL;
r = event_add_child_pidref(event, &exit_source, &ctx->pid, WEXITED, helper_on_exit, ctx);
if (r < 0)
return log_error_errno(r, "Failed to add child process to event loop: %m");
@@ -1105,20 +1088,6 @@ static int run_callout(
if (r < 0)
return log_error_errno(r, "Failed to take ownership of child process: %m");
/* Propagate sd_notify calls */
r = sd_event_add_io(event, &notify_source, fd, EPOLLIN, helper_on_notify, TAKE_PTR(ctx));
if (r < 0)
return log_error_errno(r, "Failed to add notification propagation to event loop: %m");
(void) sd_event_source_set_description(notify_source, "notify-socket");
(void) sd_event_source_set_priority(notify_source, SD_EVENT_PRIORITY_NORMAL - 5);
r = sd_event_source_set_io_fd_own(notify_source, true);
if (r < 0)
return log_error_errno(r, "Event loop failed to take ownership of notification source: %m");
TAKE_FD(fd);
/* Process events until the helper quits */
return sd_event_loop(event);
}
+9 -34
View File
@@ -46,7 +46,7 @@ typedef struct Manager {
Hashmap *polkit_registry;
sd_event_source *notify_event;
char *notify_socket_path;
RuntimeScope runtime_scope; /* For now only RUNTIME_SCOPE_SYSTEM */
} Manager;
@@ -456,7 +456,7 @@ static int job_start(Job *j) {
};
size_t k = 2;
if (setenv("NOTIFY_SOCKET", "/run/systemd/sysupdate/notify", /* overwrite= */ 1) < 0) {
if (setenv("NOTIFY_SOCKET", j->manager->notify_socket_path, /* overwrite= */ 1) < 0) {
log_error_errno(errno, "setenv() failed: %m");
_exit(EXIT_FAILURE);
}
@@ -1637,7 +1637,7 @@ static Manager *manager_free(Manager *m) {
hashmap_free(m->jobs);
m->bus = sd_bus_flush_close_unref(m->bus);
sd_event_source_unref(m->notify_event);
free(m->notify_socket_path);
sd_event_unref(m->event);
return mfree(m);
@@ -1698,11 +1698,6 @@ static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void
static int manager_new(Manager **ret) {
_cleanup_(manager_freep) Manager *m = NULL;
_cleanup_close_ int notify_fd = -EBADF;
static const union sockaddr_union sa = {
.un.sun_family = AF_UNIX,
.un.sun_path = "/run/systemd/sysupdate/notify",
};
int r;
assert(ret);
@@ -1738,35 +1733,15 @@ static int manager_new(Manager **ret) {
if (r < 0)
return r;
notify_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (notify_fd < 0)
return -errno;
(void) mkdir_parents_label(sa.un.sun_path, 0755);
(void) sockaddr_un_unlink(&sa.un);
if (bind(notify_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0)
return -errno;
r = setsockopt_int(notify_fd, SOL_SOCKET, SO_PASSCRED, true);
r = notify_socket_prepare(
m->event,
SD_EVENT_PRIORITY_NORMAL,
manager_on_notify,
m,
&m->notify_socket_path);
if (r < 0)
return r;
r = setsockopt_int(notify_fd, SOL_SOCKET, SO_PASSPIDFD, true);
if (r < 0)
log_debug_errno(r, "Failed to enable SO_PASSPIDFD, ignoring: %m");
r = sd_event_add_io(m->event, &m->notify_event, notify_fd, EPOLLIN, manager_on_notify, m);
if (r < 0)
return r;
(void) sd_event_source_set_description(m->notify_event, "notify-socket");
r = sd_event_source_set_io_fd_own(m->notify_event, true);
if (r < 0)
return r;
TAKE_FD(notify_fd);
*ret = TAKE_PTR(m);
return 0;
}
+1
View File
@@ -132,6 +132,7 @@ simple_tests += files(
'test-modhex.c',
'test-mountpoint-util.c',
'test-net-naming-scheme.c',
'test-notify-recv.c',
'test-nsresource.c',
'test-nulstr-util.c',
'test-open-file.c',
+91
View File
@@ -0,0 +1,91 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <unistd.h>
#include "sd-daemon.h"
#include "event-util.h"
#include "notify-recv.h"
#include "path-util.h"
#include "process-util.h"
#include "rm-rf.h"
#include "tests.h"
#include "tmpfile-util.h"
typedef struct Context {
unsigned data;
PidRef pidref;
} Context;
static void context_done(Context *c) {
assert(c);
pidref_done(&c->pidref);
}
static int on_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
Context *c = ASSERT_PTR(userdata);
_cleanup_(pidref_done) PidRef sender = PIDREF_NULL;
_cleanup_strv_free_ char **l = NULL;
ASSERT_OK(notify_recv_strv(fd, &l, /* ret_ucred= */ NULL, &sender));
ASSERT_TRUE(pidref_equal(&c->pidref, &sender));
_cleanup_free_ char *joined = strv_join(l, ", ");
ASSERT_NOT_NULL(joined);
log_info("Received message: %s", joined);
if (strv_contains(l, "FIRST_MESSAGE=1")) {
ASSERT_STREQ(l[0], "FIRST_MESSAGE=1");
ASSERT_NULL(l[1]);
ASSERT_EQ(c->data, 0u);
} else if (strv_contains(l, "SECOND_MESSAGE=1")) {
ASSERT_STREQ(l[0], "SECOND_MESSAGE=1");
ASSERT_STREQ(l[1], "ADDITIONAL_DATA=hoge");
ASSERT_EQ(c->data, 1u);
}
c->data++;
return 0;
}
static int on_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata) {
Context *c = ASSERT_PTR(userdata);
ASSERT_EQ(si->si_code, CLD_EXITED);
ASSERT_EQ(si->si_status, EXIT_SUCCESS);
ASSERT_EQ(c->data, 2u);
return sd_event_exit(sd_event_source_get_event(s), 0);
}
TEST(notify_socket_prepare) {
int r;
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
ASSERT_OK(sd_event_new(&e));
_cleanup_(context_done) Context c = {
.pidref = PIDREF_NULL,
};
_cleanup_free_ char *path = NULL;
ASSERT_OK(notify_socket_prepare(e, SD_EVENT_PRIORITY_NORMAL - 10, on_recv, &c, &path) >= 0);
ASSERT_OK(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD));
ASSERT_OK(r = pidref_safe_fork("(test-notify-recv-child)", FORK_DEATHSIG_SIGTERM|FORK_LOG, &c.pidref));
if (r == 0) {
ASSERT_OK_ERRNO(setenv("NOTIFY_SOCKET", path, /* overwrite = */ true));
ASSERT_OK_POSITIVE(sd_notify(/* unset_environment = */ false, "FIRST_MESSAGE=1"));
ASSERT_OK_POSITIVE(sd_notify(/* unset_environment = */ false, "FIRST_MESSAGE=2\nADDITIONAL_DATA=hoge"));
_exit(EXIT_SUCCESS);
}
ASSERT_OK(event_add_child_pidref(e, NULL, &c.pidref, WEXITED, on_sigchld, &c));
ASSERT_OK(sd_event_loop(e));
}
DEFINE_TEST_MAIN(LOG_DEBUG);