From 1f4b90e84fdc90e65e776d6549dfcd4302209701 Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Wed, 26 Feb 2025 15:46:55 -0800 Subject: [PATCH 1/2] libutil: add sd_notify() Problem: it's a pain to add a dependency on libsystemd-dev just to call sd_notify(). Add a standalone implementation of sd_notify() from the sd_notify(3) man page, modified slightly for Flux. --- src/libutil/Makefile.am | 4 +- src/libutil/sd_notify.c | 115 ++++++++++++++++++++++++++++++++++++++++ src/libutil/sd_notify.h | 22 ++++++++ 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/libutil/sd_notify.c create mode 100644 src/libutil/sd_notify.h diff --git a/src/libutil/Makefile.am b/src/libutil/Makefile.am index 7ca89aed..36893d00 100644 --- a/src/libutil/Makefile.am +++ b/src/libutil/Makefile.am @@ -36,7 +36,9 @@ libutil_la_SOURCES = \ path.c \ path.h \ argsplit.c \ - argsplit.h + argsplit.h \ + sd_notify.c \ + sd_notify.h TESTS = \ test_hash.t \ diff --git a/src/libutil/sd_notify.c b/src/libutil/sd_notify.c new file mode 100644 index 00000000..682f5656 --- /dev/null +++ b/src/libutil/sd_notify.c @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: MIT-0 */ +/* Implement the systemd notify protocol without external dependencies. + * Supports both readiness notification on startup and on reloading, + * according to the protocol defined at: + * https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html + * This protocol is guaranteed to be stable as per: + * https://systemd.io/PORTABILITY_AND_STABILITY/ */ + +/* From https://www.man7.org/linux/man-pages/man3/sd_notify.3.html + * Modified for Flux - 2025-02-26 jg */ + +#if HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sd_notify.h" + +#define _cleanup_(f) __attribute__((cleanup(f))) + +static void closep(int *fd) +{ + if (!fd || *fd < 0) + return; + close(*fd); + *fd = -1; +} + +// flag is currently ignored +int sd_notify (int flag, const char *message) +{ + union sockaddr_union { + struct sockaddr sa; + struct sockaddr_un sun; + } socket_addr = { + .sun.sun_family = AF_UNIX, + }; + size_t path_length, message_length; + _cleanup_ (closep) int fd = -1; + const char *socket_path; + + /* Verify the argument first */ + if (!message) + return -EINVAL; + + message_length = strlen (message); + if (message_length == 0) + return -EINVAL; + + /* If the variable is not set, the protocol is a noop */ + socket_path = getenv ("NOTIFY_SOCKET"); + if (!socket_path) + return 0; /* Not set? Nothing to do */ + + /* Only AF_UNIX is supported, with path or abstract sockets */ + if (socket_path[0] != '/' && socket_path[0] != '@') + return -EAFNOSUPPORT; + + path_length = strlen(socket_path); + /* Ensure there is room for NUL byte */ + if (path_length >= sizeof(socket_addr.sun.sun_path)) + return -E2BIG; + + memcpy (socket_addr.sun.sun_path, socket_path, path_length); + + /* Support for abstract socket */ + if (socket_addr.sun.sun_path[0] == '@') + socket_addr.sun.sun_path[0] = 0; + + fd = socket (AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); + if (fd < 0) + return -errno; + + if (connect (fd, + &socket_addr.sa, + offsetof(struct sockaddr_un, sun_path) + path_length) != 0) + return -errno; + + ssize_t written = write (fd, message, message_length); + if (written != (ssize_t)message_length) + return written < 0 ? -errno : -EPROTO; + + return 1; /* Notified! */ +} + +int sd_notifyf (int flag, const char *fmt, ...) +{ + va_list ap; + char *message = NULL; + int rc; + + va_start (ap, fmt); + rc = vasprintf (&message, fmt, ap); + va_end (ap); + if (rc < 0) + return -errno; + + rc = sd_notify (flag, message); + free (message); + return rc; +} + +// vi:ts=4 sw=4 expandtab diff --git a/src/libutil/sd_notify.h b/src/libutil/sd_notify.h new file mode 100644 index 00000000..dd3d90a8 --- /dev/null +++ b/src/libutil/sd_notify.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: MIT-0 */ +/* Implement the systemd notify protocol without external dependencies. + * Supports both readiness notification on startup and on reloading, + * according to the protocol defined at: + * https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html + * This protocol is guaranteed to be stable as per: + * https://systemd.io/PORTABILITY_AND_STABILITY/ */ + +/* From https://www.man7.org/linux/man-pages/man3/sd_notify.3.html + * Modified for Flux - 2025-02-26 jg */ + +#ifndef LIBUTIL_SD_NOTIFY_H +#define LIBUTIL_SD_NOTIFY_H + +#include + +int sd_notify (int flag, const char *message); +int sd_notifyf (int flag, const char *fmt, ...); + +#endif //!LIBUTIL_SD_NOTIFY_H + +// vi:ts=4 sw=4 expandtab From 346fcc5f454d65d5fd190f90572ae80872d467ee Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Mon, 24 Feb 2025 15:18:18 -0800 Subject: [PATCH 2/2] imp: call sd_notify() in exec Problem: the IMP should support running with type=notify when launched by sdexec as the main process of a systemd unit. Call sd_notify(3) at various points to support this. The calls should be no-ops if running another way. READY=1 After shell process has been spawned. This transitions the unit to running state. STOPPING=1 After shell process has terminated. This transitions the unit to deactivating state. STATUS= Provide human readable status at those two points, including the exit status of the shell in the latter. --- src/imp/exec/exec.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/imp/exec/exec.c b/src/imp/exec/exec.c index c880995d..d07770f6 100644 --- a/src/imp/exec/exec.c +++ b/src/imp/exec/exec.c @@ -36,6 +36,7 @@ #include #include "src/libutil/kv.h" +#include "src/libutil/sd_notify.h" #include "src/lib/context.h" #include "src/lib/sign.h" @@ -282,6 +283,11 @@ int imp_exec_privileged (struct imp_state *imp, struct kv *kv) imp_exec (exec); } + int rc = sd_notify (0, "READY=1"); + if (rc < 0) + imp_warn ("sd_notify READY=1 failed: %s", strerror (-rc)); + sd_notify (0, "STATUS=IMP is monitoring child and forwarding signals"); + /* Ensure common signals received by this IMP are forwarded to * the child process */ @@ -293,9 +299,31 @@ int imp_exec_privileged (struct imp_state *imp, struct kv *kv) imp_die (1, "waitpid: %s", strerror (errno)); } + rc = sd_notify (0, "STOPPING=1"); + if (rc < 0) + imp_warn ("sd_notify STOPPING=1 failed: %s", strerror (-rc)); + if (WIFEXITED (status)) { + sd_notifyf (0, + "STATUS=IMP child exited (%d), waiting for cgroup", + WEXITSTATUS (status)); + } + else if (WIFSIGNALED (status)) { + sd_notifyf (0, + "STATUS=IMP child %s, waiting for cgroup", + strsignal (WTERMSIG (status))); + } + else { + sd_notifyf (0, + "STATUS=IMP child wait returned status=%d," + " waiting for cgroup", + status); + } + if (cgroup_wait_for_empty (exec->imp->cgroup) < 0) imp_warn ("error waiting for processes in job cgroup"); + sd_notify (0, "STATUS=cgroup is now empty, exiting"); + #if HAVE_PAM /* Call privliged IMP plugins/containment finalization */ if (imp_supports_pam (exec))