]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
man: add self-contained example of notify protocol 32030/head
authorLuca Boccassi <bluca@debian.org>
Sun, 31 Mar 2024 22:18:09 +0000 (23:18 +0100)
committerLuca Boccassi <bluca@debian.org>
Tue, 2 Apr 2024 13:53:31 +0000 (14:53 +0100)
We are saying in public that the protocl is stable and can be easily
reimplemented, so provide an example doing so in the documentation,
license as MIT-0 so that it can be copied and pasted at will.

docs/PORTABILITY_AND_STABILITY.md
man/notify-selfcontained-example.c [new file with mode: 0644]
man/sd_notify.xml
man/systemd.service.xml

index abdc3dc65802d2eade83b4c9855a9d95edf7d6b9..fda42c84c056fc2ab49de0b8f6c367817a3a543b 100644 (file)
@@ -15,7 +15,11 @@ The stable interfaces are:
 
 * **The command line interface** of `systemd`, `systemctl`, `loginctl`, `journalctl`, and all other command line utilities installed in `$PATH` and documented in a man page. We will make sure that scripts invoking these commands will continue to work with future versions of systemd. Note however that the output generated by these commands is generally not included in the promise, unless it is documented in the man page. Example: the output of `systemctl status` is not stable, but that of `systemctl show` is, because the former is intended to be human readable and the latter computer readable, and this is documented in the man page.
 
-* **The protocol spoken on the socket referred to by `$NOTIFY_SOCKET`**, as documented in [sd_notify(3)](https://www.freedesktop.org/software/systemd/man/sd_notify.html).
+* **The protocol spoken on the socket referred to by `$NOTIFY_SOCKET`**, as documented in
+  [sd_notify(3)](https://www.freedesktop.org/software/systemd/man/sd_notify.html). Note that, although using
+  libsystemd is a good choice, this protocol can also be reimplemented without external dependencies, as
+  demonstrated in the example listed in
+  [systemd.service(5)](https://www.freedesktop.org/software/systemd/man/systemd.service.html)
 
 * Some of the **"special" unit names** and their semantics. To be precise the ones that are necessary for normal services, and not those required only for early boot and late shutdown, with very few exceptions. To list them here: `basic.target`, `shutdown.target`, `sockets.target`, `network.target`, `getty.target`, `graphical.target`, `multi-user.target`, `rescue.target`, `emergency.target`, `poweroff.target`, `reboot.target`, `halt.target`, `runlevel[1-5].target`.
 
diff --git a/man/notify-selfcontained-example.c b/man/notify-selfcontained-example.c
new file mode 100644 (file)
index 0000000..9a7553e
--- /dev/null
@@ -0,0 +1,173 @@
+/* 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/ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <time.h>
+#include <unistd.h>
+
+#define _cleanup_(f) __attribute__((cleanup(f)))
+
+static void closep(int *fd) {
+  if (!fd || *fd < 0)
+    return;
+
+  close(*fd);
+  *fd = -1;
+}
+
+static int notify(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;
+
+  socket_path = getenv("NOTIFY_SOCKET");
+  if (!socket_path)
+    return 0; /* Not running under systemd? Nothing to do */
+
+  if (!message)
+    return -EINVAL;
+
+  message_length = strlen(message);
+  if (message_length == 0)
+    return -EINVAL;
+
+  /* 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! */
+}
+
+static int notify_ready(void) {
+  return notify("READY=1");
+}
+
+static int notify_reloading(void) {
+  /* A buffer with length sufficient to format the maximum UINT64 value. */
+  char reload_message[sizeof("RELOADING=1\nMONOTONIC_USEC=18446744073709551615")];
+  struct timespec ts;
+  uint64_t now;
+
+  /* Notify systemd that we are reloading, including a CLOCK_MONOTONIC timestamp in usec
+   * so that the program is compatible with a Type=notify-reload service. */
+
+  if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
+    return -errno;
+
+  if (ts.tv_sec < 0 || ts.tv_nsec < 0 ||
+      (uint64_t) ts.tv_sec > (UINT64_MAX - (ts.tv_nsec / 1000ULL)) / 1000000ULL)
+    return -EINVAL;
+
+  now = (uint64_t) ts.tv_sec * 1000000ULL + (uint64_t) ts.tv_nsec / 1000ULL;
+
+  if (snprintf(reload_message, sizeof(reload_message), "RELOADING=1\nMONOTONIC_USEC=%" PRIu64, now) < 0)
+    return -EINVAL;
+
+  return notify(reload_message);
+}
+
+static volatile sig_atomic_t reloading = 0;
+static volatile sig_atomic_t terminating = 0;
+
+static void signal_handler(int sig) {
+  if (sig == SIGHUP)
+    reloading = 1;
+  else if (sig == SIGINT || sig == SIGTERM)
+    terminating = 1;
+}
+
+int main(int argc, char **argv) {
+  struct sigaction sa = {
+    .sa_handler = signal_handler,
+    .sa_flags = SA_RESTART,
+  };
+  int r;
+
+  /* Setup signal handlers */
+  sigemptyset(&sa.sa_mask);
+  sigaction(SIGHUP, &sa, NULL);
+  sigaction(SIGINT, &sa, NULL);
+  sigaction(SIGTERM, &sa, NULL);
+
+  /* Do more service initialization work here … */
+
+  /* Now that all the preparations steps are done, signal readiness */
+
+  r = notify_ready();
+  if (r < 0) {
+    fprintf(stderr, "Failed to notify readiness to $NOTIFY_SOCKET: %s\n", strerror(-r));
+    return EXIT_FAILURE;
+  }
+
+  while (!terminating) {
+    if (reloading) {
+      reloading = false;
+
+      /* As a separate but related feature, we can also notify the manager
+       * when reloading configuration. This allows accurate state-tracking,
+       * and also automated hook-in of 'systemctl reload' without having to
+       * specify manually an ExecReload= line in the unit file. */
+
+      r = notify_reloading();
+      if (r < 0) {
+        fprintf(stderr, "Failed to notify reloading to $NOTIFY_SOCKET: %s\n", strerror(-r));
+        return EXIT_FAILURE;
+      }
+
+      /* Do some reconfiguration work here … */
+
+      r = notify_ready();
+      if (r < 0) {
+        fprintf(stderr, "Failed to notify readiness to $NOTIFY_SOCKET: %s\n", strerror(-r));
+        return EXIT_FAILURE;
+      }
+    }
+
+    /* Do some daemon work here … */
+    sleep(5);
+  }
+
+  return EXIT_SUCCESS;
+}
index 1e611fe6d86e0cf37872720070e54209c6c899b6..22b351cad31f5ce55914ddfa7f628b7a44049848 100644 (file)
     privileged port (i.e.: lower than 1024), as an attempt to address concerns that unprivileged processes in
     the guest might try to send malicious notifications to the host, driving it to make destructive decisions
     based on them.</para>
+
+    <para>Note that, while using this library should be preferred in order to avoid code duplication, it is
+    also possible to reimplement the simple readiness notification protocol without external dependencies,
+    as demonstrated in the following self-contained example:
+    <programlisting><xi:include href="notify-selfcontained-example.c" parse="text"/></programlisting></para>
   </refsect1>
 
   <refsect1>
index 67a3930a28a578be78fb360cd5d3375802822033..9a0c8166effae381973fd19970bc1ad73c6c0d1e 100644 (file)
@@ -1728,7 +1728,7 @@ SystemdService=simple-dbus-service.service</programlisting>
 Description=Simple notifying service
 
 [Service]
-Type=notify
+Type=notify-reload
 ExecStart=/usr/sbin/simple-notifying-service
 
 [Install]
@@ -1746,6 +1746,16 @@ WantedBy=multi-user.target</programlisting>
       <citerefentry><refentrytitle>systemd.kill</refentrytitle><manvolnum>5</manvolnum></citerefentry>
       for details on how you can influence the way systemd terminates
       the service.</para>
+
+      <para>To avoid code duplication, it is preferable to use
+      <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+      when possible, especially when other APIs provided by
+      <citerefentry><refentrytitle>libsystemd</refentrytitle><manvolnum>3</manvolnum></citerefentry> are
+      also used, but note that the notification protocol is very simple and guaranteed to be stable as per
+      the <ulink url="https://systemd.io/PORTABILITY_AND_STABILITY/">Interface Portability and Stability
+      Promise</ulink>, so it can be reimplemented by services with no external dependencies. For a
+      self-contained example, see
+      <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para>
     </example>
   </refsect1>