Systemd Notifications on a Budget
Recently, the inclusion of libsystemd
as a dependency of distribution vendored copies of OpenSSH server caused a bit of a scare. Not necessarily because of libsystemd
. That being said, maybe NIH isn't that bad in some situations, maybe we shouldn't be so eager to add a dependency when a short bit of code is sufficient. So I wrote a short bit of code.
// SPDX-License-Identifier: CC0-1.0
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
int notify_socket_send(const char *message)
{
const char *path = getenv("NOTIFY_SOCKET");
if (path == NULL || path[0] == '\0') return 0;
struct sockaddr_un sa = {.sun_family = AF_UNIX};
size_t len = strnlen(path, sizeof sa.sun_path);
if (len >= sizeof sa.sun_path) return ENAMETOOLONG;
memcpy(sa.sun_path, path, len);
if (sa.sun_path[0] == '@') sa.sun_path[0] = '\0';
size_t addrlen = sa.sun_path[0] == '\0' ? sizeof sa.sun_family + len
: sizeof sa;
int fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (fd == -1) return errno;
ssize_t ret = sendto(fd, message, strlen(message), 0, (void *)&sa, addrlen);
int errno_save = errno;
close(fd);
return ret == -1 ? errno_save : 0;
}
Please do let me know about all the bugs you find. I'm sure there's at least one.
Updates on 2024-04-01: There were bugs and I have now gotten around to compiling the code. This version latest also incorporates some suggestions from Leah Neukirchen and Rich Felker (dalias). The changes are as follows:
- The function has a new name so as to not imply that the notification protocol needs to be systemd specific.
- The code now specifically handles an empty but set environment variable (currently the same way as an unset one).
- Dalias suggested using
strnlen
to avoid checking too far into the string. This is a bit of a micro-optimisation but I felt like it was fair to incorporate anyway, the reasoning is sound. - Dalias also suggested just using
errno
numbers, since the code already depends on posix for unix sockets so I can depend on POSIX for theirerrno
numbers. I am not sure I 100% like the way I ended up interpreting this advice, as some error numbers from socket overlap with those from sendto. But the overlaps shouldn't be too confusing. Instead of the_strerror
function, use the normalstrerror
function on the return value to get insight into the error.
If you happen to like negative returns for errors, feel free to add some minus signs. - Leah checked the old code to make sure it didn't have any obvious errors, and confirmed it didn't, but then I changed the code anyway so it's possible I've introduced new bugs.
- I've further considered whether it makes sense to check the positive return value of
sendto
, and I believe that (and I don't seem to be alone in this)sendto
will error withEMSGSIZE
if the datagram is simply too big. The docs are a bit vague but datagrams shouldn't ever be getting split on send (on receipt you lose what you didn't budget for in your receive buffer, unless you peek). - I implemented support (hopefully correctly) for abstract sockets. This requires some complexity around how the length is handled. Abstract sockets can't be the full length of
sun_path
but IIRCsd_notify
has the same limitation.