Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

man: document that using sd_journal APIs might cause dlopen to happen and add self-contained notify protocol example #32030

Merged
merged 2 commits into from Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/PORTABILITY_AND_STABILITY.md
Expand Up @@ -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`.

Expand Down
173 changes: 173 additions & 0 deletions man/notify-selfcontained-example.c
@@ -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;
bluca marked this conversation as resolved.
Show resolved Hide resolved
}

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;
bluca marked this conversation as resolved.
Show resolved Hide resolved

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);

bluca marked this conversation as resolved.
Show resolved Hide resolved
/* 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);
bluca marked this conversation as resolved.
Show resolved Hide resolved
}

return EXIT_SUCCESS;
}
11 changes: 11 additions & 0 deletions man/sd-journal.xml
Expand Up @@ -86,6 +86,17 @@
— are fully thread-safe and may be called from multiple threads in parallel.</para>
</refsect1>

<refsect1>
<title>Optional dependencies</title>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the rationale for documenting this? this seems to be somewhat of an implementation detail of things, and we usually don't document these.

Also, isn't this kinda incomplete? Are you sure, sd-jounral is the only API that does this, and that the list is complete?

I'd be more comfortable if this was auto-generated and thus always correct, but this seems a bit random to me?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, it'd work better as its own PR. It doesn't fit naturally in this one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add it: if people don't know/think about dlopen, they can be quite confused. It applies to all of libsystemd though. This would fit much better in libsystemd(3), in the NOTES section.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rationale is that with the switch of various libs to dlopen, it will be harder to understand who does what in the public library when external programs link to it and call its APIs. In the abscence of a better way to inspect this, documenting it is the least we can do.

And I put it in sd-journal intentionally to clarify the scope: we know that it's only the sd-journal component of the library that loads these, so it's a safe and stable declaration that we can make, so that users don't get the impression that, say, using sd_bus for their dbus calls also results in these dlopens.


<para>Depending on which build-time options are enabled, functions that operate on
<structname>sd_journal</structname> objects might cause optional shared libraries to be dynamically
loaded via
<citerefentry project='man7'><refentrytitle>dlopen</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
such as decompression libraries (xz, lz4, zstd) or cryptographic libraries (gcrypt).
</para>
</refsect1>

<xi:include href="libsystemd-pkgconfig.xml" />

<refsect1>
Expand Down
5 changes: 5 additions & 0 deletions man/sd_notify.xml
Expand Up @@ -493,6 +493,11 @@
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
Copy link

@ya-isakov ya-isakov Apr 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought, based on https://mastodon.social/@pid_eins/112202687764571433, using this library just for notify is not recommended? @poettering

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>
Expand Down
12 changes: 11 additions & 1 deletion man/systemd.service.xml
Expand Up @@ -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]
Expand All @@ -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
bluca marked this conversation as resolved.
Show resolved Hide resolved
<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>

Expand Down