From 1775d9857a7ac60578f3560613ead7c189d8b756 Mon Sep 17 00:00:00 2001
From: Julien Moutinho <julm+tmux@sourcephile.fr>
Date: Wed, 16 Mar 2022 02:43:18 +0100
Subject: [PATCH] Add support for systemd socket activation

See http://0pointer.de/blog/projects/socket-activation.html
and https://www.freedesktop.org/software/systemd/man/sd_listen_fds.html

Fixes #3117
---
 Makefile.am      |  5 ++++
 compat.h         |  5 ++++
 compat/systemd.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++
 configure.ac     | 24 +++++++++++++++++++
 server.c         | 22 +++++++++++-------
 tmux.c           |  1 +
 tmux.h           |  2 ++
 7 files changed, 111 insertions(+), 8 deletions(-)
 create mode 100644 compat/systemd.c

diff --git a/Makefile.am b/Makefile.am
index 68494932..5bdd9d5f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -204,6 +204,11 @@ if NEED_FORKPTY
 nodist_tmux_SOURCES += compat/forkpty-@PLATFORM@.c
 endif
 
+# Add compat file for systemd.
+if HAVE_SYSTEMD
+nodist_tmux_SOURCES += compat/systemd.c
+endif
+
 # Add compat file for utf8proc.
 if HAVE_UTF8PROC
 nodist_tmux_SOURCES += compat/utf8proc.c
diff --git a/compat.h b/compat.h
index be726831..6eb97619 100644
--- a/compat.h
+++ b/compat.h
@@ -421,6 +421,11 @@ void		*reallocarray(void *, size_t, size_t);
 void		*recallocarray(void *, size_t, size_t, size_t);
 #endif
 
+#ifdef HAVE_SYSTEMD
+/* systemd.c */
+int		 systemd_create_socket(int, char **);
+#endif
+
 #ifdef HAVE_UTF8PROC
 /* utf8proc.c */
 int		 utf8proc_wcwidth(wchar_t);
diff --git a/compat/systemd.c b/compat/systemd.c
new file mode 100644
index 00000000..8e34c007
--- /dev/null
+++ b/compat/systemd.c
@@ -0,0 +1,60 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2022 Nicholas Marriott <nicholas.marriott@gmail.com>
+ * Copyright (c) 2022 Julien Moutinho <julm+tmux@sourcephile.fr>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <systemd/sd-daemon.h>
+
+#include "tmux.h"
+
+int
+systemd_create_socket(int flags, char **cause)
+{
+	int			fds;
+	int			fd;
+	struct sockaddr_un	sa;
+	int			addrlen = sizeof sa;
+
+	fds = sd_listen_fds(0);
+	if (fds > 1) { /* too many file descriptors */
+		errno = E2BIG;
+		goto fail;
+	}
+
+	if (fds == 1) { /* socket-activated */
+		fd = SD_LISTEN_FDS_START;
+		if (!sd_is_socket_unix(fd, SOCK_STREAM, 1, NULL, 0)) {
+			errno = EPFNOSUPPORT;
+			goto fail;
+		}
+		if (getsockname(fd, (struct sockaddr *)&sa, &addrlen) == -1)
+			goto fail;
+		socket_path = xstrdup(sa.sun_path);
+		socket_can_be_created_again = 0;
+		return (fd);
+	}
+
+	return (server_create_socket(flags, cause));
+
+fail:
+	if (cause != NULL)
+		xasprintf(cause, "systemd socket error (%s)", strerror(errno));
+	return (-1);
+}
diff --git a/configure.ac b/configure.ac
index e473f141..f90d9f05 100644
--- a/configure.ac
+++ b/configure.ac
@@ -390,6 +390,30 @@ if test "x$enable_utf8proc" = xyes; then
 fi
 AM_CONDITIONAL(HAVE_UTF8PROC, [test "x$enable_utf8proc" = xyes])
 
+# Check for systemd support.
+AC_ARG_ENABLE(
+	systemd,
+	AS_HELP_STRING(--enable-systemd, enable systemd integration)
+)
+if test x"$enable_systemd" = xyes; then
+	PKG_CHECK_MODULES(
+		SYSTEMD,
+		libsystemd,
+		[
+			AM_CPPFLAGS="$SYSTEMD_CFLAGS $AM_CPPFLAGS"
+			CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
+			LIBS="$SYSTEMD_LIBS $LIBS"
+			AC_DEFINE(HAVE_SYSTEMD)
+			found_systemd=yes
+		],
+		found_systemd=no
+	)
+	if test "x$found_systemd" = xno; then
+		AC_MSG_ERROR("systemd not found")
+	fi
+fi
+AM_CONDITIONAL(HAVE_SYSTEMD, [test "x$found_systemd" = xyes])
+
 # Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well.
 AC_MSG_CHECKING(for b64_ntop)
 	AC_LINK_IFELSE([AC_LANG_PROGRAM(
diff --git a/server.c b/server.c
index 2db5a8d8..5acddb10 100644
--- a/server.c
+++ b/server.c
@@ -100,7 +100,7 @@ server_check_marked(void)
 }
 
 /* Create server socket. */
-static int
+int
 server_create_socket(int flags, char **cause)
 {
 	struct sockaddr_un	sa;
@@ -214,7 +214,11 @@ server_start(struct tmuxproc *client, int flags, struct event_base *base,
 
 	gettimeofday(&start_time, NULL);
 
+#ifdef HAVE_SYSTEMD
+	server_fd = systemd_create_socket(flags, &cause);
+#else
 	server_fd = server_create_socket(flags, &cause);
+#endif
 	if (server_fd != -1)
 		server_update_socket();
 	if (~flags & CLIENT_NOFORK)
@@ -424,14 +428,16 @@ server_signal(int sig)
 		server_child_signal();
 		break;
 	case SIGUSR1:
-		event_del(&server_ev_accept);
-		fd = server_create_socket(server_client_flags, NULL);
-		if (fd != -1) {
-			close(server_fd);
-			server_fd = fd;
-			server_update_socket();
+		if (socket_can_be_created_again) {
+			event_del(&server_ev_accept);
+			fd = server_create_socket(server_client_flags, NULL);
+			if (fd != -1) {
+				close(server_fd);
+				server_fd = fd;
+				server_update_socket();
+			}
+			server_add_accept(0);
 		}
-		server_add_accept(0);
 		break;
 	case SIGUSR2:
 		proc_toggle_log(server_proc);
diff --git a/tmux.c b/tmux.c
index 11c368ff..187a7429 100644
--- a/tmux.c
+++ b/tmux.c
@@ -40,6 +40,7 @@ struct environ	*global_environ;
 
 struct timeval	 start_time;
 const char	*socket_path;
+int		 socket_can_be_created_again = 1;
 int		 ptm_fd = -1;
 const char	*shell_command;
 
diff --git a/tmux.h b/tmux.h
index 370c7773..d5c63390 100644
--- a/tmux.h
+++ b/tmux.h
@@ -2001,6 +2001,7 @@ extern struct options	*global_s_options;
 extern struct options	*global_w_options;
 extern struct environ	*global_environ;
 extern struct timeval	 start_time;
+extern int		 socket_can_be_created_again;
 extern const char	*socket_path;
 extern const char	*shell_command;
 extern int		 ptm_fd;
@@ -2583,6 +2584,7 @@ int	 server_start(struct tmuxproc *, int, struct event_base *, int, char *);
 void	 server_update_socket(void);
 void	 server_add_accept(int);
 void printflike(1, 2) server_add_message(const char *, ...);
+int	 server_create_socket(int, char **);
 
 /* server-client.c */
 RB_PROTOTYPE(client_windows, client_window, entry, server_client_window_cmp);
-- 
2.34.1