From c9f3af7dc4accc4da352ef8ffdb95478bcf2bd16 Mon Sep 17 00:00:00 2001 From: Nicolas Graves Date: Sat, 13 Apr 2024 19:37:34 +0200 Subject: [PATCH] Implement systemd socket. --- configure.ac | 19 +----- lib/Makefile.in | 2 +- lib/gnulib.mk.in | 2 - lib/sd-socket.c | 149 +++++++++++++++++++++++++++++++++++++++++++++++ lib/sd-socket.h | 127 ++++++++++++++++++++++++++++++++++++++++ msdos/sed1v2.inp | 3 - src/Makefile.in | 9 +-- src/deps.mk | 2 +- src/emacs.c | 50 ++++++++-------- 9 files changed, 308 insertions(+), 55 deletions(-) create mode 100644 lib/sd-socket.c create mode 100644 lib/sd-socket.h diff --git a/configure.ac b/configure.ac index 29b71ea2730..a245a7b5ccc 100644 --- a/configure.ac +++ b/configure.ac @@ -457,7 +457,6 @@ AC_DEFUN OPTION_DEFAULT_ON([webp],[don't compile with WebP image support]) OPTION_DEFAULT_ON([sqlite3],[don't compile with sqlite3 support]) OPTION_DEFAULT_ON([lcms2],[don't compile with Little CMS support]) -OPTION_DEFAULT_ON([libsystemd],[don't compile with libsystemd support]) OPTION_DEFAULT_ON([cairo],[don't compile with Cairo drawing]) OPTION_DEFAULT_OFF([cairo-xcb], [use XCB surfaces for Cairo support]) OPTION_DEFAULT_ON([xml2],[don't compile with XML parsing support]) @@ -3203,21 +3202,6 @@ AC_DEFUN AC_SUBST([LIBGNUTLS_LIBS]) AC_SUBST([LIBGNUTLS_CFLAGS]) -HAVE_LIBSYSTEMD=no -if test "${with_libsystemd}" = "yes" ; then - dnl This code has been tested with libsystemd 222 and later. - dnl FIXME: Find the earliest version number for which Emacs should work, - dnl and change '222' to that number. - EMACS_CHECK_MODULES([LIBSYSTEMD], [libsystemd >= 222], - [HAVE_LIBSYSTEMD=yes], [HAVE_LIBSYSTEMD=no]) - if test "${HAVE_LIBSYSTEMD}" = "yes"; then - AC_DEFINE([HAVE_LIBSYSTEMD], [1], [Define if using libsystemd.]) - fi -fi - -AC_SUBST([LIBSYSTEMD_LIBS]) -AC_SUBST([LIBSYSTEMD_CFLAGS]) - HAVE_JSON=no JSON_OBJ= @@ -6652,7 +6636,7 @@ AC_DEFUN optsep= emacs_config_features= for opt in ACL BE_APP CAIRO DBUS FREETYPE GCONF GIF GLIB GMP GNUTLS GPM GSETTINGS \ - HARFBUZZ IMAGEMAGICK JPEG JSON LCMS2 LIBOTF LIBSELINUX LIBSYSTEMD LIBXML2 \ + HARFBUZZ IMAGEMAGICK JPEG JSON LCMS2 LIBOTF LIBSELINUX LIBXML2 \ M17N_FLT MODULES NATIVE_COMP NOTIFY NS OLDXMENU PDUMPER PGTK PNG RSVG SECCOMP \ SOUND SQLITE3 THREADS TIFF TOOLKIT_SCROLL_BARS TREE_SITTER \ UNEXEC WEBP X11 XAW3D XDBE XFT XIM XINPUT2 XPM XWIDGETS X_TOOLKIT \ @@ -6721,7 +6705,6 @@ AC_DEFUN Does Emacs use -lm17n-flt? ${HAVE_M17N_FLT} Does Emacs use -lotf? ${HAVE_LIBOTF} Does Emacs use -lxft? ${HAVE_XFT} - Does Emacs use -lsystemd? ${HAVE_LIBSYSTEMD} Does Emacs use -ljansson? ${HAVE_JSON} Does Emacs use -ltree-sitter? ${HAVE_TREE_SITTER} Does Emacs use the GMP library? ${HAVE_GMP} diff --git a/lib/Makefile.in b/lib/Makefile.in index 71199c32277..d934a755e85 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -74,7 +74,7 @@ Makefile: not_emacs_OBJECTS = regex.o malloc/%.o free.o libgnu_a_OBJECTS = fingerprint.o $(gl_LIBOBJS) \ - $(patsubst %.c,%.o,$(filter %.c,$(libgnu_a_SOURCES))) + $(patsubst %.c,%.o,$(filter %.c,$(libgnu_a_SOURCES))) sd-socket.o for_emacs_OBJECTS = $(filter-out $(not_emacs_OBJECTS),$(libgnu_a_OBJECTS)) libegnu_a_OBJECTS = $(patsubst %.o,e-%.o,$(for_emacs_OBJECTS)) diff --git a/lib/gnulib.mk.in b/lib/gnulib.mk.in index 9ab4b741595..f9ed4a4cb23 100644 --- a/lib/gnulib.mk.in +++ b/lib/gnulib.mk.in @@ -912,8 +912,6 @@ LIBSECCOMP_CFLAGS = @LIBSECCOMP_CFLAGS@ LIBSECCOMP_LIBS = @LIBSECCOMP_LIBS@ LIBSELINUX_LIBS = @LIBSELINUX_LIBS@ LIBSOUND = @LIBSOUND@ -LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@ -LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@ LIBS_ECLIENT = @LIBS_ECLIENT@ LIBS_GNUSTEP = @LIBS_GNUSTEP@ LIBS_MAIL = @LIBS_MAIL@ diff --git a/lib/sd-socket.c b/lib/sd-socket.c new file mode 100644 index 00000000000..6350882006d --- /dev/null +++ b/lib/sd-socket.c @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Copied and adapted from systemd source code. */ + +#define _GNU_SOURCE 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sd-socket.h" +#define _cleanup_(f) __attribute__((cleanup(f))) + +int sd_is_socket(int fd, int type, int listening) { + struct stat st_fd; + + assert(fd >= 0); + assert(type >= 0); + + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISSOCK(st_fd.st_mode)) + return 0; + + if (type != 0) { + int other_type = 0; + socklen_t l = sizeof(other_type); + + if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0) + return -errno; + + if (l != sizeof(other_type)) + return -EINVAL; + + if (other_type != type) + return 0; + } + + if (listening >= 0) { + int accepting = 0; + socklen_t l = sizeof(accepting); + + if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0) + return -errno; + + if (l != sizeof(accepting)) + return -EINVAL; + + if (!accepting != !listening) + return 0; + } + + return 1; +} + +/* 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/ */ + +static void closep(int *fd) { + if (!fd || *fd < 0) + return; + + close(*fd); + *fd = -1; +} + +static int sd_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; + + /* Verify the argument first */ + if (!message) + return -EINVAL; + + message_length = strlen(message); + if (message_length == 0) + return -EINVAL; + + /* If the variable is not set, the protocol is a noop */ + socket_path = getenv("NOTIFY_SOCKET"); + if (!socket_path) + return 0; /* Not set? Nothing to do */ + + /* 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! */ +} + +int sd_notify_ready(void) { + return sd_notify("READY=1"); +} + +int sd_notify_stopping(void) { + return sd_notify("STOPPING=1"); +} diff --git a/lib/sd-socket.h b/lib/sd-socket.h new file mode 100644 index 00000000000..31450c731e9 --- /dev/null +++ b/lib/sd-socket.h @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Copied and adapted from systemd source code. */ +#ifndef _SD_SOCKET_H +#define _SD_SOCKET_H 1 + +#include +#include +#include + +/* + The following functionality is provided: + + - Support for logging with log levels on stderr + - File descriptor passing for socket-based activation + - Daemon startup and status notification + - Detection of systemd boots + + See sd-daemon(3) for more information. +*/ + +/* The first passed file descriptor is fd 3 */ +#define SD_LISTEN_FDS_START 3 + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a socket of type (SOCK_DGRAM, SOCK_STREAM, + ...), 0 otherwise. If type is 0 a socket type check will not be done + and the call only verifies if the file descriptor refers to a + socket. If listening is > 0 it is verified that the socket is in + listening mode. (i.e. listen() has been called) If listening is == 0 + it is verified that the socket is not in listening mode. If + listening is < 0 no listening mode check is done. Returns a negative + errno style error code on failure. + + See sd_is_socket(3) for more information. +*/ +int sd_is_socket(int fd, int type, int listening); + +/* + Informs systemd about changed daemon state. This takes a number of + newline separated environment-style variable assignments in a + string. The following variables are known: + + MAINPID=... The main PID of a daemon, in case systemd did not + fork off the process itself. Example: "MAINPID=4711" + + READY=1 Tells systemd that daemon startup or daemon reload + is finished (only relevant for services of Type=notify). + The passed argument is a boolean "1" or "0". Since there + is little value in signaling non-readiness the only + value daemons should send is "READY=1". + + RELOADING=1 Tell systemd that the daemon began reloading its + configuration. When the configuration has been + reloaded completely, READY=1 should be sent to inform + systemd about this. + + STOPPING=1 Tells systemd that the daemon is about to go down. + + STATUS=... Passes a single-line status string back to systemd + that describes the daemon state. This is free-form + and can be used for various purposes: general state + feedback, fsck-like programs could pass completion + percentages and failing programs could pass a human + readable error message. Example: "STATUS=Completed + 66% of file system check..." + + NOTIFYACCESS=... + Reset the access to the service status notification socket. + Example: "NOTIFYACCESS=main" + + ERRNO=... If a daemon fails, the errno-style error code, + formatted as string. Example: "ERRNO=2" for ENOENT. + + BUSERROR=... If a daemon fails, the D-Bus error-style error + code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut" + + WATCHDOG=1 Tells systemd to update the watchdog timestamp. + Services using this feature should do this in + regular intervals. A watchdog framework can use the + timestamps to detect failed services. Also see + sd_watchdog_enabled() below. + + WATCHDOG_USEC=... + Reset watchdog_usec value during runtime. + To reset watchdog_usec value, start the service again. + Example: "WATCHDOG_USEC=20000000" + + FDSTORE=1 Store the file descriptors passed along with the + message in the per-service file descriptor store, + and pass them to the main process again on next + invocation. This variable is only supported with + sd_pid_notify_with_fds(). + + FDSTOREREMOVE=1 + Remove one or more file descriptors from the file + descriptor store, identified by the name specified + in FDNAME=, see below. + + FDNAME= A name to assign to new file descriptors stored in the + file descriptor store, or the name of the file descriptors + to remove in case of FDSTOREREMOVE=1. + + Daemons can choose to send additional variables. However, it is + recommended to prefix variable names not listed above with X_. + + Returns a negative errno-style error code on failure. Returns > 0 + if systemd could be notified, 0 if it couldn't possibly because + systemd is not running. + + Example: When a daemon finished starting up, it could issue this + call to notify systemd about it: + + sd_notify(0, "READY=1"); + + See sd_notifyf() for more complete examples. + + See sd_notify(3) for more information. +*/ + +/* This is actually a simplified version that you can find right there : + https://www.freedesktop.org/software/systemd/man/devel/sd_notify.html#Notes + */ +int sd_notify_ready(void); +int sd_notify_stopping(void); + +#endif diff --git a/msdos/sed1v2.inp b/msdos/sed1v2.inp index 4d4c80a6b1a..d6d80ff809c 100644 --- a/msdos/sed1v2.inp +++ b/msdos/sed1v2.inp @@ -138,8 +138,6 @@ s/ *@WEBP_LIBS@// /^LIBMODULES *=/s/@LIBMODULES@// /^MODULES_OBJ *=/s/@MODULES_OBJ@// /^LIBSELINUX_LIBS *=/s/@LIBSELINUX_LIBS@// -/^LIBSYSTEMD_LIBS *=/s/@LIBSYSTEMD_LIBS@// -/^LIBSYSTEMD_CFLAGS *=/s/@LIBSYSTEMD_CFLAGS@// /^LIB_CLOCK_GETTIME *=/s/@[^@\n]*@//g /^LIB_TIMER_TIME *=/s/@[^@\n]*@//g /^LIB_EXECINFO *=/s/@[^@\n]*@//g @@ -269,7 +267,6 @@ s/echo.*buildobj.lst/dj&/ # Make the GCC command line fit one screen line /^[ ][ ]*\$(GNUSTEP_CFLAGS)/d /^[ ][ ]*\$(LIBGNUTLS_CFLAGS)/d -/^[ ][ ]*\$(LIBSYSTEMD_CFLAGS)/d /^[ ][ ]*\$(XRANDR_CFLAGS)/d /^[ ][ ]*\$(WEBKIT_CFLAGS)/d /^[ ][ ]*\$(SETTINGS_CFLAGS)/d diff --git a/src/Makefile.in b/src/Makefile.in index 9bc53c072ea..d3a7f432ea8 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -336,9 +336,6 @@ LIBSELINUX_LIBS = LIBGNUTLS_LIBS = @LIBGNUTLS_LIBS@ LIBGNUTLS_CFLAGS = @LIBGNUTLS_CFLAGS@ -LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@ -LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@ - JSON_LIBS = @JSON_LIBS@ JSON_CFLAGS = @JSON_CFLAGS@ JSON_OBJ = @JSON_OBJ@ @@ -405,11 +402,11 @@ EMACS_CFLAGS= $(C_SWITCH_MACHINE) $(C_SWITCH_SYSTEM) $(C_SWITCH_X_SITE) \ $(GNUSTEP_CFLAGS) $(CFLAGS_SOUND) $(RSVG_CFLAGS) $(IMAGEMAGICK_CFLAGS) \ $(PNG_CFLAGS) $(LIBXML2_CFLAGS) $(LIBGCCJIT_CFLAGS) $(DBUS_CFLAGS) \ - $(XRANDR_CFLAGS) $(XINERAMA_CFLAGS) $(XFIXES_CFLAGS) $(XDBE_CFLAGS) \ + $(XRANDR_CFLAGS) $(XINERAMA_CFLAGS) $(XFIXES_CFLAGS) $(XDBE_CFAGS) \ $(XINPUT_CFLAGS) $(WEBP_CFLAGS) $(WEBKIT_CFLAGS) $(LCMS2_CFLAGS) \ $(SETTINGS_CFLAGS) $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) \ $(HARFBUZZ_CFLAGS) $(LIBOTF_CFLAGS) $(M17N_FLT_CFLAGS) $(DEPFLAGS) \ - $(LIBSYSTEMD_CFLAGS) $(JSON_CFLAGS) $(XSYNC_CFLAGS) $(TREE_SITTER_CFLAGS) \ + $(JSON_CFLAGS) $(XSYNC_CFLAGS) $(TREE_SITTER_CFLAGS) \ $(LIBGNUTLS_CFLAGS) $(NOTIFY_CFLAGS) $(CAIRO_CFLAGS) \ $(WERROR_CFLAGS) $(HAIKU_CFLAGS) $(XCOMPOSITE_CFLAGS) $(XSHAPE_CFLAGS) ALL_CFLAGS = $(EMACS_CFLAGS) $(WARN_CFLAGS) $(CFLAGS) @@ -567,7 +564,7 @@ LIBES = $(LIBS_TERMCAP) $(GETLOADAVG_LIBS) $(SETTINGS_LIBS) $(LIBSELINUX_LIBS) \ $(FREETYPE_LIBS) $(FONTCONFIG_LIBS) $(HARFBUZZ_LIBS) $(LIBOTF_LIBS) $(M17N_FLT_LIBS) \ $(LIBGNUTLS_LIBS) $(LIB_PTHREAD) $(GETADDRINFO_A_LIBS) $(LCMS2_LIBS) \ - $(NOTIFY_LIBS) $(LIB_MATH) $(LIBZ) $(LIBMODULES) $(LIBSYSTEMD_LIBS) \ + $(NOTIFY_LIBS) $(LIB_MATH) $(LIBZ) $(LIBMODULES) \ $(JSON_LIBS) $(LIBGMP) $(LIBGCCJIT_LIBS) $(XINPUT_LIBS) $(HAIKU_LIBS) \ $(TREE_SITTER_LIBS) $(SQLITE3_LIBS) $(XCOMPOSITE_LIBS) $(XSHAPE_LIBS) diff --git a/src/deps.mk b/src/deps.mk index a7c8ae11f72..b932764f267 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -92,7 +92,7 @@ editfns.o: emacs.o: emacs.c commands.h systty.h syssignal.h blockinput.h process.h \ termhooks.h buffer.h atimer.h systime.h $(INTERVALS_H) lisp.h $(config_h) \ globals.h ../lib/unistd.h window.h dispextern.h keyboard.h keymap.h \ - frame.h coding.h gnutls.h msdos.h dosfns.h unexec.h + frame.h coding.h gnutls.h msdos.h dosfns.h unexec.h ../lib/sd-socket.h fileio.o: fileio.c window.h buffer.h systime.h $(INTERVALS_H) character.h \ coding.h msdos.h blockinput.h atimer.h lisp.h $(config_h) frame.h \ commands.h globals.h ../lib/unistd.h diff --git a/src/emacs.c b/src/emacs.c index dde305edbc2..c0381c23c37 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -57,10 +57,8 @@ #define MAIN_PROGRAM #include "dosfns.h" #endif -#ifdef HAVE_LIBSYSTEMD -# include -# include -#endif +#include "sd-socket.h" +#include #if defined HAVE_LINUX_SECCOMP_H && defined HAVE_LINUX_FILTER_H \ && HAVE_DECL_SECCOMP_SET_MODE_FILTER \ @@ -1719,20 +1717,19 @@ main (int argc, char **argv) } } /* daemon_type == 2 */ -#ifdef HAVE_LIBSYSTEMD /* Read the number of sockets passed through by systemd. */ - int systemd_socket = sd_listen_fds (1); - - if (systemd_socket > 1) - fputs (("\n" - "Warning: systemd passed more than one socket to Emacs.\n" - "Try 'Accept=false' in the Emacs socket unit file.\n"), - stderr); - else if (systemd_socket == 1 - && (0 < sd_is_socket (SD_LISTEN_FDS_START, - AF_UNSPEC, SOCK_STREAM, 1))) - sockfd = SD_LISTEN_FDS_START; -#endif /* HAVE_LIBSYSTEMD */ + const char *fds = getenv("LISTEN_FDS"); + if (fds) { + int systemd_socket = strtol(fds, NULL, 0); + if (systemd_socket > 1) + fputs (("\n" + "Warning: systemd passed more than one socket to Emacs.\n" + "Try 'Accept=false' in the Emacs socket unit file.\n"), + stderr); + else if (systemd_socket == 1 + && (0 < sd_is_socket (SD_LISTEN_FDS_START, SOCK_STREAM, 1))) + sockfd = SD_LISTEN_FDS_START; + } /* On X, the bug happens because we call abort to avoid GLib crashes upon a longjmp in our X error handler. @@ -2857,12 +2854,15 @@ DEFUN ("kill-emacs", Fkill_emacs, Skill_emacs, 0, 2, "P", } #endif -#ifdef HAVE_LIBSYSTEMD /* Notify systemd we are shutting down, but only if we have notified it about startup. */ - if (daemon_type == -1) - sd_notify(0, "STOPPING=1"); -#endif /* HAVE_LIBSYSTEMD */ + if (daemon_type == -1){ + int r = sd_notify_stopping(); + if (r < 0) { + fprintf(stderr, "Failed to report termination to $NOTIFY_SOCKET: %s\n", strerror(-r)); + exit (EXIT_FAILURE); + } + } /* Fsignal calls emacs_abort () if it sees that waiting_for_input is set. */ @@ -3382,9 +3382,11 @@ DEFUN ("daemon-initialized", Fdaemon_initialized, Sdaemon_initialized, 0, 0, 0, if (daemon_type == 1) { -#ifdef HAVE_LIBSYSTEMD - sd_notify(0, "READY=1"); -#endif /* HAVE_LIBSYSTEMD */ + int r = sd_notify_ready(); + if (r < 0) { + fprintf(stderr, "Failed to notify readiness to $NOTIFY_SOCKET: %s\n", strerror(-r)); + exit (EXIT_FAILURE); + } } if (daemon_type == 2) -- 2.41.0