From: Alyssa Ross <hi@alyssa.is>
To: Demi Marie Obenour <demiobenour@gmail.com>
Cc: Spectrum OS Development <devel@spectrum-os.org>
Subject: Re: [PATCH v2 1/3] tools: Add adapter tool for services using sd_notify
Date: Thu, 25 Sep 2025 12:29:20 +0200 [thread overview]
Message-ID: <87348addvj.fsf@alyssa.is> (raw)
In-Reply-To: <20250924-udev-v2-1-6089de521b3b@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 10513 bytes --]
Demi Marie Obenour <demiobenour@gmail.com> writes:
> This adapts programs using sd_notify for use with s6 readiness
> notification.
>
> I chose to use Linux-specific epoll(7). It makes the code simpler and
> more readable. Also, stdin and stdout are hardcoded. This is in the
> interest of simplicity.
I personally find poll to be more readable when working with a fixed set
of descriptors, but it's up to you!
>
> Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
> ---
> systemd readiness notification has two
> strict advantages over the s6 version:
>
> 1. It allows reliable reloading.
> 2. It allows providing a status message that the service manager
> can show in status output.
>
> s6 would actually benefit from both of these features.
> ---
> Changes since v1:
>
> - Hard-code file descriptors.
> - Run wrapper as background process.
> - Massively reduce code size.
> - Use // instead of /* */ for comments.
> - Check that the notification FD is a pipe and that the listening socket
> is a socket.
> - Rely on s6-ipc-socketbinder to create the listening socket.
> - Do not unlink the listening socket.
> ---
> tools/default.nix | 1 +
> tools/meson.build | 1 +
> tools/sd-notify-adapter/meson.build | 4 +
> tools/sd-notify-adapter/sd-notify-adapter.c | 206 ++++++++++++++++++++++++++++
> 4 files changed, 212 insertions(+)
>
> diff --git a/tools/sd-notify-adapter/meson.build b/tools/sd-notify-adapter/meson.build
> new file mode 100644
> index 0000000000000000000000000000000000000000..6032a3a7704d49cae0655b43d0189444d3b15e4d
> --- /dev/null
> +++ b/tools/sd-notify-adapter/meson.build
> @@ -0,0 +1,4 @@
> +# SPDX-License-Identifier: ISC
> +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
> +
> +executable('sd-notify-adapter', 'sd-notify-adapter.c', install: true)
Why the non-standard license?
> diff --git a/tools/sd-notify-adapter/sd-notify-adapter.c b/tools/sd-notify-adapter/sd-notify-adapter.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..661e3f41e57dae97a5cfaeb3a7088b0c67235563
> --- /dev/null
> +++ b/tools/sd-notify-adapter/sd-notify-adapter.c
> @@ -0,0 +1,206 @@
> +// SPDX-License-Identifier: MIT
> +// SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
> +// check_posix and check_posix_bool are based on playpen.c, which has
> +// the license:
> +//
> +// Copyright 2014 Daniel Micay
> +//
> +// Permission is hereby granted, free of charge, to any person obtaining a
> +// copy of this software and associated documentation files (the
> +// "Software"), to deal in the Software without restriction, including
> +// without limitation the rights to use, copy, modify, merge, publish,
> +// distribute, sublicense, and/or sell copies of the Software, and to
> +// permit persons to whom the Software is furnished to do so, subject to
> +// the following conditions:
> +//
> +// The above copyright notice and this permission notice shall be included
> +// in all copies or substantial portions of the Software.
> +//
> +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
> +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
> +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
> +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
> +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
> +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
> +
> +#define _GNU_SOURCE 1
> +#include <assert.h>
> +#include <errno.h>
> +#include <limits.h>
> +#include <signal.h>
> +#include <stdarg.h>
> +#include <stddef.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <err.h>
> +#include <fcntl.h>
> +#include <sys/epoll.h>
> +#include <sys/socket.h>
> +#include <sys/stat.h>
> +#include <sys/un.h>
> +#include <sysexits.h>
> +#include <unistd.h>
> +
> +#define ARRAY_SIZE(s) (sizeof(s)/sizeof(s[0]))
> +
> +// TODO: does this need to have credit given to Daniel Micay?
> +[[gnu::format(printf, 2, 3), gnu::warn_unused_result]]
> +static intmax_t check_posix(intmax_t arg, const char *fmt, ...) {
> + if (arg >= 0)
> + return arg;
> + assert(arg == -1);
> + va_list a;
> + va_start(a, fmt);
> + verr(EX_OSERR, fmt, a);
> +}
> +
> +#define check_posix(arg, message, ...) \
> + ((__typeof__(arg))check_posix(arg, message, ## __VA_ARGS__))
> +
> +// And same here
> +[[gnu::format(printf, 2, 3)]]
> +static void check_posix_bool(intmax_t arg, const char *fmt, ...) {
> + if (arg != -1) {
> + assert(arg == 0);
> + return;
> + }
> + va_list a;
> + va_start(a, fmt);
> + verr(EX_OSERR, fmt, a);
> + va_end(a); // Not reached
> +}
I would prefer that we do manual error checks in the style of other C
code in Spectrum. Then we don't have to worry about licensing of these
helpers, and also don't have the problem of how to share them between
multiple files later on. It's likely that readers are also going to be
more familiar with simple error checks.
> +
> +static bool ready;
> +
> +enum {
> + socket_fd,
> + notification_fd,
> +};
> +
> +static void
> +process_notification(struct iovec *const msg, const char *const initial_buffer) {
> + ssize_t data = recv(socket_fd, msg->iov_base, msg->iov_len,
> + MSG_DONTWAIT | MSG_TRUNC | MSG_PEEK);
> + if (data == -1) {
> + if (errno == EINTR) {
> + return; // signal caught
> + }
> + if (errno == EAGAIN || errno == EWOULDBLOCK) {
> + return; // spurious wakeup
> + }
> + }
> + size_t size = (size_t)check_posix(data, "recv");
> + if (size > (size_t)INT_MAX) {
> + // cannot happen on Linux, don't bother implementing
> + size = (size_t)INT_MAX;
> + }
If it can't happen, why do we branch on it?
> + if (size > msg->iov_len) {
> + char *b = (msg->iov_base == initial_buffer) ?
> + malloc(size) : realloc(msg->iov_base, size);
> + if (b != NULL) {
> + msg->iov_base = b;
> + msg->iov_len = size;
> + }
> + }
Wouldn't it be simpler to pass an empty iov, then allocate whatever size
we need here, than to have to handle sometimes having a stack-allocated
buffer and sometimes not?
> + size = (size_t)check_posix(recv(socket_fd, msg->iov_base, msg->iov_len,
> + MSG_CMSG_CLOEXEC | MSG_DONTWAIT | MSG_TRUNC),
> + "recv");
> + const char *cursor = msg->iov_base;
> + const char *const end = cursor + size;
> + for (char *next; cursor != NULL; cursor = (next == NULL ? NULL : next + 1)) {
> + next = memchr(cursor, '\n', (size_t)(end - cursor));
> + size_t message_size = (size_t)((next == NULL ? end : next) - cursor);
> +
> + // TODO: avoid repeating sizeof(string)
Yeah, let's maybe pull the message we're looking for out into a constant.
> + if (message_size == sizeof("READY=1") - 1 &&
> + memcmp(cursor, "READY=1", sizeof("READY=1") - 1) == 0) {
> + if (check_posix(write(notification_fd, "\n", 1), "write") != 1)
> + assert(0);
> + exit(0);
> + }
> + }
> +}
> +
> +int main(int argc, char **argv [[gnu::unused]]) {
> + if (argc != 1) {
> + errx(EX_USAGE, "stdin is listening socket, stdout is notification pipe");
> + }
> + struct stat info;
> + check_posix_bool(fstat(notification_fd, &info), "fstat");
> + if (!S_ISFIFO(info.st_mode)) {
> + errx(EX_USAGE, "notification descriptor is not a pipe");
> + }
> + int value;
> + socklen_t len = sizeof(value);
> + int status = getsockopt(socket_fd, SOL_SOCKET, SO_DOMAIN, &value, &len);
> + if (status == -1 && errno == ENOTSOCK) {
> + errx(EX_USAGE, "socket fd is not a socket");
> + }
> + check_posix_bool(status, "getsockopt");
> + assert(len == sizeof(value));
> + if (value != AF_UNIX) {
> + errx(EX_USAGE, "socket fd must be AF_UNIX socket");
> + }
> + check_posix_bool(getsockopt(socket_fd, SOL_SOCKET, SO_TYPE, &value, &len),
> + "getsockopt");
> + assert(len == sizeof(value));
> + if (value != SOCK_DGRAM) {
> + errx(EX_USAGE, "socket must be datagram socket");
> + }
> +
I think these checks are overly defensive. It's going to be very
difficult to use this program wrong given it's always going to be used
in the same way in run scripts. I'd rather have less code, which will
make it easier to understand what the actual functionality of the
program is.
> + // Ignore SIGPIPE.
> + struct sigaction act = { };
> + act.sa_handler = SIG_IGN;
> + check_posix_bool(sigaction(SIGPIPE, &act, NULL), "sigaction(SIGPIPE)");
Wouldn't SIGPIPE be useful here? Isn't the default behavior of exiting
on SIGPIPE exactly what we'd want to do?
> +
> + // Open file descriptors.
> + int epoll_fd = check_posix(epoll_create1(EPOLL_CLOEXEC), "epoll_create1");
> + if (epoll_fd < 3) {
> + errx(EX_USAGE, "Invoked with file descriptor 0, 1, or 2 closed");
> + }
> + struct epoll_event event = { .events = EPOLLIN, .data.u64 = socket_fd };
> + check_posix_bool(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &event),
> + "epoll_ctl");
> + event = (struct epoll_event) { .events = 0, .data.u64 = notification_fd };
> + check_posix_bool(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, notification_fd, &event),
> + "epoll_ctl");
> +
> + // Main event loop.
> + char buf[sizeof("READY=1\n") - 1];
> + struct iovec v = {
> + .iov_base = buf,
> + .iov_len = sizeof(buf),
> + };
> + for (;;) {
> + struct epoll_event out_event[2] = {};
> + int epoll_wait_result =
> + check_posix(epoll_wait(epoll_fd, out_event, ARRAY_SIZE(out_event), -1),
> + "epoll_wait");
> + for (int i = 0; i < epoll_wait_result; ++i) {
> + switch (out_event[i].data.u64) {
> + case socket_fd:
> + if (out_event[i].events != EPOLLIN) {
> + errx(EX_PROTOCOL, "Unexpected event from epoll() on notification socket");
> + }
> + process_notification(&v, buf);
> + break;
> + case notification_fd:
> + if (out_event[i].events != EPOLLERR) {
> + errx(EX_SOFTWARE, "Unexpected event from epoll() on supervison pipe");
> + }
> + if (ready) {
> + // Normal exit
> + return 0;
> + }
> + errx(EX_PROTOCOL, "s6 closed its pipe before the child was ready");
> + break;
Why do we need to poll on notification_fd at all? If it closes early,
we get a write fail or a SIGPIPE, and we exit with a failure or are
killed, which is fine, right?
> + default:
> + assert(0); // Not reached
> + }
> + }
> + }
> +}
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]
next prev parent reply other threads:[~2025-09-25 10:29 UTC|newest]
Thread overview: 51+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-09-14 3:16 [PATCH 0/3] Switch from mdevd to systemd-udevd in root filesystem Demi Marie Obenour
2025-09-14 3:16 ` [PATCH 1/3] host/rootfs: Add early serial output Demi Marie Obenour
2025-09-17 11:45 ` Alyssa Ross
2025-09-18 2:44 ` Demi Marie Obenour
2025-09-19 14:21 ` Alyssa Ross
2025-09-19 14:49 ` Alyssa Ross
2025-09-14 3:16 ` [PATCH 2/3] tools: Add adapter tool for services using sd_notify Demi Marie Obenour
2025-09-14 3:16 ` [PATCH 3/3] host/rootfs: switch to systemd-udevd Demi Marie Obenour
2025-09-19 14:12 ` Alyssa Ross
2025-09-19 19:32 ` Demi Marie Obenour
2025-09-21 12:18 ` Alyssa Ross
2025-09-21 17:02 ` Demi Marie Obenour
2025-09-21 16:27 ` Demi Marie Obenour
2025-09-21 16:28 ` Demi Marie Obenour
2025-09-23 18:39 ` Alyssa Ross
2025-09-23 19:18 ` Demi Marie Obenour
2025-09-24 10:32 ` [PATCH v2 0/3] Switch from mdevd to systemd-udevd in root filesystem Demi Marie Obenour
2025-09-24 10:32 ` [PATCH v2 1/3] tools: Add adapter tool for services using sd_notify Demi Marie Obenour
2025-09-25 10:29 ` Alyssa Ross [this message]
2025-09-25 16:54 ` Demi Marie Obenour
2025-09-24 10:32 ` [PATCH v2 2/3] host/rootfs: Switch to systemd-udevd Demi Marie Obenour
2025-09-25 10:53 ` Alyssa Ross
2025-09-25 17:53 ` Demi Marie Obenour
2025-09-26 14:56 ` Alyssa Ross
2025-09-28 22:51 ` [PATCH v3 0/2] Switch from mdevd to systemd-udevd in root filesystem Demi Marie Obenour
2025-09-28 22:51 ` [PATCH v3 1/2] tools: Add adapter tool for services using sd_notify Demi Marie Obenour
2025-10-01 16:06 ` Alyssa Ross
2025-09-28 22:51 ` [PATCH v3 2/2] host/rootfs: Switch to systemd-udevd Demi Marie Obenour
2025-10-01 14:24 ` Alyssa Ross
2025-10-01 14:39 ` Alyssa Ross
2025-10-01 17:40 ` Demi Marie Obenour
2025-10-02 9:53 ` Alyssa Ross
2025-10-02 10:34 ` Alyssa Ross
2025-10-02 10:36 ` [PATCH v3 0/2] Switch from mdevd to systemd-udevd in root filesystem Alyssa Ross
2025-10-03 21:42 ` [PATCH v4 " Demi Marie Obenour
2025-10-03 21:42 ` [PATCH v4 1/2] tools: Add adapter tool for services using sd_notify Demi Marie Obenour
2025-10-28 15:38 ` Alyssa Ross
2025-10-28 22:56 ` Demi Marie Obenour
2025-10-29 11:26 ` Alyssa Ross
2025-10-31 4:34 ` Demi Marie Obenour
2025-10-31 8:54 ` Alyssa Ross
2025-11-01 18:23 ` Demi Marie Obenour
2025-10-03 21:42 ` [PATCH v4 2/2] host/rootfs: Switch to systemd-udevd Demi Marie Obenour
2025-10-28 16:02 ` Alyssa Ross
2025-10-28 22:56 ` Demi Marie Obenour
2025-10-29 9:31 ` Alyssa Ross
2025-10-29 9:55 ` Demi Marie Obenour
2025-09-24 10:32 ` [PATCH v2 3/3] host/rootfs: Simplify s6-rc dependencies Demi Marie Obenour
2025-09-25 11:07 ` Alyssa Ross
2025-09-25 15:50 ` Demi Marie Obenour
2025-10-02 10:37 ` Alyssa Ross
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87348addvj.fsf@alyssa.is \
--to=hi@alyssa.is \
--cc=demiobenour@gmail.com \
--cc=devel@spectrum-os.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://spectrum-os.org/git/crosvm
https://spectrum-os.org/git/doc
https://spectrum-os.org/git/mktuntap
https://spectrum-os.org/git/nixpkgs
https://spectrum-os.org/git/spectrum
https://spectrum-os.org/git/ucspi-vsock
https://spectrum-os.org/git/www
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).