From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from atuin.qyliss.net (localhost [IPv6:::1]) by atuin.qyliss.net (Postfix) with ESMTP id 2AF9811CCE; Wed, 24 Sep 2025 10:34:11 +0000 (UTC) Received: by atuin.qyliss.net (Postfix, from userid 993) id 60A8911D13; Wed, 24 Sep 2025 10:34:07 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-26) on atuin.qyliss.net X-Spam-Level: X-Spam-Status: No, score=-0.1 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,DMARC_PASS,FREEMAIL_FROM,RCVD_IN_DNSWL_NONE, SPF_HELO_NONE autolearn=unavailable autolearn_force=no version=4.0.1 Received: from mail-yw1-x1133.google.com (mail-yw1-x1133.google.com [IPv6:2607:f8b0:4864:20::1133]) by atuin.qyliss.net (Postfix) with ESMTPS id 8B51611D0B for ; Wed, 24 Sep 2025 10:34:05 +0000 (UTC) Received: by mail-yw1-x1133.google.com with SMTP id 00721157ae682-74109e2ed70so33198327b3.3 for ; Wed, 24 Sep 2025 03:34:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1758710044; x=1759314844; darn=spectrum-os.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=G1fujiaZAbne0PysBhJQJK8AAggWcfn2knjiyWu+AMw=; b=QkU5eJhitM31ljBSLS5hCmzrdLVwfMP6LXZcJGQ/C80hZVcPZAsY5Go8NDND5zTlNI 6ms4OuOSvPQhzKlPlm30FeyoRH9kUtO73ApWOSlxEtaS+Tjz/5ngLahojPcvbkVhnYqx eZtjYbt6I1+I7ndudTno4lWHou5Ur72VpWX+2jwXAbguHQRfOVOoLxThSUjJPgIRiWAT MB8dvqrd+PS5D7dW9plo/jpalioa7lznAlWq+sR+6HN4+fYxRxHDbst8C5PouCuwC0Ib n2QD64mP3OaZ6SDQzgyrA/rzog3aveUehqZV2pWri7/LiAvos8dyArPP897EnFMxpYWp zVEQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758710044; x=1759314844; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=G1fujiaZAbne0PysBhJQJK8AAggWcfn2knjiyWu+AMw=; b=T3e/f+DiTB0dCZmxfpO27JSXUbc+ClYm8JNVvAoNiwN1bBhxmRYl6a0m4dGLqGqEjg omyCeJDPfUCYfZTZOHmlC1zz+O2IMwuHe7P5qiyy+nJ7sj+SUG1pOisI5DOLXelUWjuZ U0uWfyhc6hSj3/s6ouOPFBwa39p0CyvtyR1V8LmKCyatnSoCuvczSM0vlePsSZsNRQjW aBrk8kJ0K826Xq1d+6OlXfS8GRJ5iUpwi6DHw3MjxbtH70e3mULUygDDpFv2B3iwwD3Y ir3RIIHIDOj8RoWgFYaiVD3gEPn23JXoViIfe3PhDtsWmFAYgQlai4hKO8jQ0nBfDqhB RcFg== X-Gm-Message-State: AOJu0YwxcbFq27W51qErJdpAdF6H5dMp9IDUJNXXZGtkMWXVNX46A9P/ /GOYIP4Z5bwDHTSTrT07u9KOa1iqRgEKnsqo5Mb5k+EJcrsAvo7NUqc8eApykQ== X-Gm-Gg: ASbGnctVCHex0V2Y+bVV4QwN6vS4gRrnflGwlN5e3i+wLLyscIjdWHMpZazwIC8v9Ce WWi+MeejB9YRYx8C+ZtsTPi7T7pq6y/L8vq8yYhBvuY50VaGO6KSi0+u2IPkV/OXBPUsUO2ja2C 9Z8V6XmjRpnb84ZF4hhvdxhKBFJpImmyKLbWtDN6rB/dryL48rIfqbGFeBmSj1t6Tj0vJ9LtSqT U7qG/EIkdyxFCGKCLY5lJsYGymvcQ7y42tg9MwzJcOLJEWOsWTTag/pI5C5kjfFmacGlEJ1Qpgr KvGR0OaM8R2TlX1PfrvDnjA+816oXKcaSWXKUV7AerFxE4YVx1Hz4h1Wlowj9ruUQ+ZxwTvEGRg yR6sPg0b7hvpTCL7gdWy9VPBnBTNT+xzJJr6hnabu2llo40k25oMv+y6tFn6zi8vOKDCpifL/uy C1ecpGQGO1Sue7JNVtriqDZbH3amladwW4Nkm3vN7NZJz94z53FXjGAA== X-Google-Smtp-Source: AGHT+IEToO6ST1sDyMNwBzkX/QL0KtdBsr6rAVF8WV7U9Dh+enErsiq4hfAFpQL/YJatr2oCXlQsdA== X-Received: by 2002:a05:690c:311:b0:751:9542:7372 with SMTP id 00721157ae682-758944988d0mr45795777b3.16.1758710044228; Wed, 24 Sep 2025 03:34:04 -0700 (PDT) Received: from localhost.localdomain (h96-60-249-169.cncrtn.broadband.dynamic.tds.net. [96.60.249.169]) by smtp.gmail.com with UTF8SMTPSA id 00721157ae682-75d312ee6d1sm6608337b3.56.2025.09.24.03.34.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 24 Sep 2025 03:34:03 -0700 (PDT) From: Demi Marie Obenour Date: Wed, 24 Sep 2025 06:32:38 -0400 Subject: [PATCH v2 1/3] tools: Add adapter tool for services using sd_notify MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20250924-udev-v2-1-6089de521b3b@gmail.com> References: <20250924-udev-v2-0-6089de521b3b@gmail.com> In-Reply-To: <20250924-udev-v2-0-6089de521b3b@gmail.com> To: Spectrum OS Development X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1758709958; l=9628; i=demiobenour@gmail.com; s=20250729; h=from:subject:message-id; bh=dChNsn10fYMnfF5/W64u2ukUSPdZG3PA5hwOMS/9eow=; b=mWxo5ikYHUzZW+BuH5St4Xd3NUilMpAixkTGE77R5xkrNUQCjcbMkXkCK/o0/4tNI5D6V/muA b3msFIidvAbD9Jcv9aWH7//bxsHQV5tXz7imkGH5cTf8VJxTQdXepEY X-Developer-Key: i=demiobenour@gmail.com; a=ed25519; pk=X57Q4/YQDj9t4SBeKaDwvXYKB6quZJVx/DE2Ly2out0= Message-ID-Hash: K2HQQ63YXVAO2XHJDHT364KKUCZ6CYTJ X-Message-ID-Hash: K2HQQ63YXVAO2XHJDHT364KKUCZ6CYTJ X-MailFrom: demiobenour@gmail.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-devel.spectrum-os.org-0; header-match-devel.spectrum-os.org-1; header-match-devel.spectrum-os.org-2; header-match-devel.spectrum-os.org-3; header-match-devel.spectrum-os.org-4; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Demi Marie Obenour , Alyssa Ross X-Mailman-Version: 3.3.9 Precedence: list List-Id: Patches and low-level development discussion Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: 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. Signed-off-by: Demi Marie Obenour --- 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/default.nix b/tools/default.nix index 201afaee3c0610b62484aec9e493c3f597a8ccce..cf27ecb31d552c6223b6d332619ceeedee227aab 100644 --- a/tools/default.nix +++ b/tools/default.nix @@ -70,6 +70,7 @@ stdenv.mkDerivation (finalAttrs: { ./lsvm ./start-vmm ./subprojects + ./sd-notify-adapter ])); }; sourceRoot = "source/tools"; diff --git a/tools/meson.build b/tools/meson.build index 1fb07f02a1305883777e34d1dc2c8ceae87bf828..2393e3279f562239ece1f4d62bb4f4d5bca473ed 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -23,6 +23,7 @@ if get_option('host') subdir('lsvm') subdir('start-vmm') + subdir('sd-notify-adapter') endif if get_option('app') 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 + +executable('sd-notify-adapter', 'sd-notify-adapter.c', install: true) 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 +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +} + +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 (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; + } + } + 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) + 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"); + } + + // Ignore SIGPIPE. + struct sigaction act = { }; + act.sa_handler = SIG_IGN; + check_posix_bool(sigaction(SIGPIPE, &act, NULL), "sigaction(SIGPIPE)"); + + // 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; + default: + assert(0); // Not reached + } + } + } +} -- 2.51.0