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
strnlento 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
errnonumbers, since the code already depends on posix for unix sockets so I can depend on POSIX for theirerrnonumbers. 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_strerrorfunction, use the normalstrerrorfunction 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)sendtowill error withEMSGSIZEif 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_pathbut IIRCsd_notifyhas the same limitation.