diff --git a/test/src/Makefile.am b/test/src/Makefile.am index 226c37bd..a54bbae1 100644 --- a/test/src/Makefile.am +++ b/test/src/Makefile.am @@ -1,4 +1,4 @@ noinst_PROGRAMS = serv -serv_SOURCES = serv.c +serv_SOURCES = serv.c sd-daemon.c sd-daemon.h serv_CPPFLAGS = -D_XOPEN_SOURCE=600 -D_BSD_SOURCE -D_GNU_SOURCE -D_DEFAULT_SOURCE diff --git a/test/src/sd-daemon.c b/test/src/sd-daemon.c new file mode 100644 index 00000000..67bea2d5 --- /dev/null +++ b/test/src/sd-daemon.c @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: MIT-0 */ + +/* + * systemd notify protocol, supporting readiness notification on startup + * and 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/ + */ + +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * NOTE: As per spec., this function does not return POSIX OK(0) but + * rather "a positive value", i.e., 1. + */ +int sd_notify(int unset_environment, const char *state) +{ + struct sockaddr_un sun = { + .sun_family = AF_UNIX, + }; + size_t len, sun_len; + const char *path; + ssize_t written; + int sd; + + if (!state) + return -EINVAL; + + len = strlen(state); + if (len == 0) + return -EINVAL; + + /* If the variable is not set, the protocol is a noop */ + path = getenv("NOTIFY_SOCKET"); + if (!path) + return 0; /* Not set? Nothing to do */ + + /* Only AF_UNIX is supported, with path or abstract sockets */ + if (path[0] != '/' && path[0] != '@') + return -EAFNOSUPPORT; + + /* Ensure there is room for NUL byte */ + sun_len = strlen(path); + if (sun_len >= sizeof(sun.sun_path)) + return -E2BIG; + + strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); + if (sun.sun_path[0] == '@') + sun.sun_path[0] = 0; + + sd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (sd == -1) + return -errno; + + if (connect(sd, (struct sockaddr *)&sun, offsetof(struct sockaddr_un, sun_path) + sun_len) == -1) { + close(sd); + return -errno; + } + + if (unset_environment) + unsetenv("NOTIFY_SOCKET"); + + written = write(sd, state, len); + if (written != (ssize_t) len) { + close(sd); + return written < 0 ? -errno : -EPROTO; + } + + return 1; +} diff --git a/test/src/sd-daemon.h b/test/src/sd-daemon.h new file mode 100644 index 00000000..b9ed4a0d --- /dev/null +++ b/test/src/sd-daemon.h @@ -0,0 +1,6 @@ +#ifndef SD_DAEMON_H_ +#define SD_DAEMON_H_ + +int sd_notify(int unset_environment, const char *state); + +#endif /* SD_DAEMON_H_ */ diff --git a/test/src/serv.c b/test/src/serv.c index fbb7031c..b8ece7b9 100644 --- a/test/src/serv.c +++ b/test/src/serv.c @@ -16,6 +16,7 @@ #include #include #include +#include "sd-daemon.h" #define PROGNM "serv" @@ -160,7 +161,7 @@ static int usage(int rc) " -h Show help text (this)\n" " -i IDENT Change process identity, incl. logs, pidfile, etc.\n" " -n Run in foreground\n" - " -N SOCK Send 'READY=1\\n' on $NOTIFY_SOCKET or SOCK\n" + " -N SOCK Send '\\n' on SOCK (integer), for s6 readiness\n" " -p Create PID file despite running in foreground\n" " -P FILE Create PID file using FILE\n" " -r SVC Call initctl to restart service SVC (self)\n" @@ -170,6 +171,8 @@ static int usage(int rc) "to let the rest of the system know it's done. When the program runs\n" "in the foreground it does not create a PID file by default.\n" "\n" + "When acting as a systemd daemon, this program expects NOTIFY_SOCKET\n" + "\n" "Regardless of how This daemon is started it provides a single service\n" "to others, spice ... guarded by sandworms.\n", ident); @@ -256,13 +259,13 @@ int main(int argc, char *argv[]) if (do_pidfile > 0) pidfile(pidfn); - if (!do_notify) { - const char *sock = getenv("NOTIFY_SOCKET"); + if (!do_notify && getenv("NOTIFY_SOCKET")) + do_notify = 1; - /* systemd style */ - if (sock) - do_notify = atoi(sock); - } + if (do_notify) + inf("Will notify %s ...", notify_s6 ? "s6" : "systemd"); + else + inf("No notify socket ..."); if (do_crash) { inf("Simulating crash, exiting with code %d", EX_SOFTWARE); @@ -279,15 +282,24 @@ int main(int argc, char *argv[]) if (reloading) { if (do_notify > 0) { + /* Issue #343: see notify.sh test for more details */ inf("Delaying notify by 3 seconds for notify.sh ..."); sleep(3); - inf("Notifying Finit on socket %d, READY=1", do_notify); - if (write(do_notify, "READY=1\n", 8) == -1) - err(1, "Failed sending ready notification to Finit"); + if (notify_s6) { + inf("Notifying Finit on socket %d", do_notify); + if (write(do_notify, "\n", 1) < 1) + err(1, "Failed sending ready notification to Finit"); inf("s6 notify, closing socket %d ...", do_notify); - close(do_notify); + if (close(do_notify)) + err(1, "Failed closing notify socket"); do_notify = 0; + } else { + int rc; + + inf("Notifying Finit on NOTIFY_SOCKET"); + rc = sd_notify(0, "READY=1"); + inf("sd_notify () => %d", rc); } } if (do_pidfile > 0)