From 88a0f7397c5c72f3a6a70badff8e3c3c792aeb4f Mon Sep 17 00:00:00 2001 From: Max Gautier Date: Sat, 15 Jan 2022 16:58:32 +0100 Subject: [PATCH 1/5] Make foot able to receive a socket from its parent If the argument to --server is parsed as a number, consider it to be a file descriptor, and use that as a socket. This is necessary to be able to use socket activation with the server mode of foot. --- server.c | 117 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 91 insertions(+), 26 deletions(-) diff --git a/server.c b/server.c index 1d31abc6..2c41b479 100644 --- a/server.c +++ b/server.c @@ -1,6 +1,7 @@ #include "server.h" #include +#include #include #include @@ -19,6 +20,7 @@ #include "client-protocol.h" #include "shm.h" #include "terminal.h" +#include "util.h" #include "wayland.h" #include "xmalloc.h" @@ -425,44 +427,107 @@ err: return ret; } +static bool +prepare_socket(int fd) +{ + int flags = fcntl(fd, F_GETFD); + if (flags < 0) { + LOG_ERRNO("failed to get file descriptors flag for passed socket"); + return false; + } + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { + LOG_ERRNO("failed to set FD_CLOEXEC for passed socket"); + return false; + } + + flags = fcntl(fd, F_GETFL); + if (flags < 0) { + LOG_ERRNO("failed to get file status flags for passed socket"); + return false; + } + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { + LOG_ERRNO("failed to set non-blocking mode on passed socket"); + return false; + } + + int const socket_options[] = { SO_DOMAIN, SO_ACCEPTCONN, SO_TYPE }; + int const socket_options_values[] = { AF_UNIX, 1, SOCK_STREAM}; + char const * const socket_options_names[] = { "SO_DOMAIN", "SO_ACCEPTCONN", "SO_TYPE" }; + + xassert(ALEN(socket_options) == ALEN(socket_options_values)); + xassert(ALEN(socket_options) == ALEN(socket_options_names)); + + int socket_option = 0; + socklen_t len; + for (size_t i = 0; i < ALEN(socket_options) ; i++) { + len = sizeof(socket_option); + if (getsockopt(fd, SOL_SOCKET, socket_options[i], &socket_option, &len) == -1 || + len != sizeof(socket_option)) { + LOG_ERRNO("failed to read socket option from passed file descriptor"); + return false; + } + if (socket_option != socket_options_values[i]) { + LOG_ERR("wrong socket value for socket option '%s' on passed file descriptor", + socket_options_names[i]); + return false; + } + } + + return true; +} + struct server * server_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, struct wayland *wayl) { - int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (fd == -1) { - LOG_ERRNO("failed to create UNIX socket"); - return NULL; - } - + int fd; struct server *server = NULL; const char *sock_path = conf->server_socket_path; + char *end; - switch (try_connect(sock_path)) { - case CONNECT_FAIL: - break; + errno = 0; + fd = strtol(sock_path, &end, 10); + if (*end == '\0' && *sock_path != '\0') + { + if (!prepare_socket(fd)) + goto err; + LOG_DBG("we've been started by socket activation, using passed socket"); + sock_path = NULL; + } + else { + LOG_DBG("no suitable pre-existing socket found, creating our own"); + fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (fd == -1) { + LOG_ERRNO("failed to create UNIX socket"); + return NULL; + } - case CONNECT_SUCCESS: - LOG_ERR("%s is already accepting connections; is 'foot --server' already running", sock_path); - /* FALLTHROUGH */ + switch (try_connect(sock_path)) { + case CONNECT_FAIL: + break; - case CONNECT_ERR: - goto err; - } + case CONNECT_SUCCESS: + LOG_ERR("%s is already accepting connections; is 'foot --server' already running", sock_path); + /* FALLTHROUGH */ - unlink(sock_path); + case CONNECT_ERR: + goto err; + } - struct sockaddr_un addr = {.sun_family = AF_UNIX}; - strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path) - 1); + unlink(sock_path); - if (bind(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0) { - LOG_ERRNO("%s: failed to bind", addr.sun_path); - goto err; - } + struct sockaddr_un addr = {.sun_family = AF_UNIX}; + strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path) - 1); - if (listen(fd, 0) < 0) { - LOG_ERRNO("%s: failed to listen", addr.sun_path); - goto err; + if (bind(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0) { + LOG_ERRNO("%s: failed to bind", addr.sun_path); + goto err; + } + + if (listen(fd, 0) < 0) { + LOG_ERRNO("%s: failed to listen", addr.sun_path); + goto err; + } } server = malloc(sizeof(*server)); @@ -487,7 +552,7 @@ server_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, if (!fdm_add(fdm, fd, EPOLLIN, &fdm_server, server)) goto err; - LOG_INFO("accepting connections on %s", sock_path); + LOG_INFO("accepting connections on %s", sock_path != NULL ? sock_path : "socket provided through socket activation"); return server; -- 2.30.2 From 1783f69cbd6291a30b9ab1f4daef9159b5dadd85 Mon Sep 17 00:00:00 2001 From: Max Gautier Date: Sat, 15 Jan 2022 17:25:59 +0100 Subject: [PATCH 2/5] Adding systemd unit files for socket activation - Support for multiples concurrent instances, templated on WAYLAND_DISPLAY - Use standard input for the socket file descriptor (inetd style) - Always use the socket provided by systemd with the systemd user service - wayland-instance@.target is intended to be a special target a bit like graphical-session.target. --- foot-server@.service.in | 13 +++++++++++++ foot-server@.socket | 5 +++++ meson.build | 19 +++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 foot-server@.service.in create mode 100644 foot-server@.socket diff --git a/foot-server@.service.in b/foot-server@.service.in new file mode 100644 index 00000000..81c13bb4 --- /dev/null +++ b/foot-server@.service.in @@ -0,0 +1,13 @@ +[Service] +ExecStart=@bindir@/foot --server=0 +Environment=WAYLAND_DISPLAY=%i +NonBlocking=true +StandardInput=socket + +[Unit] +Requires=%N.socket +Description=Foot terminal server mode for WAYLAND_DISPLAY=%i +Documentation=man:foot(1) + +[Install] +WantedBy=wayland-instance@.target diff --git a/foot-server@.socket b/foot-server@.socket new file mode 100644 index 00000000..71db51cb --- /dev/null +++ b/foot-server@.socket @@ -0,0 +1,5 @@ +[Socket] +ListenStream=%t/foot-%i.sock + +[Install] +WantedBy=wayland-instance@.target diff --git a/meson.build b/meson.build index 30044f9e..8778927d 100644 --- a/meson.build +++ b/meson.build @@ -245,6 +245,25 @@ install_data( 'foot.desktop', 'foot-server.desktop', 'footclient.desktop', install_dir: join_paths(get_option('datadir'), 'applications')) +systemd = dependency('systemd', required: false) +if systemd.found() + + configuration = configuration_data() + configuration.set('bindir', join_paths(get_option('prefix'), get_option('bindir'))) + + systemd_units_dir = systemd.get_pkgconfig_variable('systemduserunitdir') + configure_file( + configuration: configuration, + input: 'foot-server@.service.in', + output: '@BASENAME@', + install_dir: systemd_units_dir + ) + + install_data( + 'foot-server@.socket', + install_dir: systemd_units_dir) +endif + scdoc = dependency('scdoc', native: true, required: get_option('docs')) if scdoc.found() install_data( -- 2.30.2 From 4ec172a4ad3e64489945a42b3bf6adb147e8e2d3 Mon Sep 17 00:00:00 2001 From: Max Gautier Date: Sat, 15 Jan 2022 17:37:31 +0100 Subject: [PATCH 3/5] Document socket activation feature - Particular mention to included systemd user units --- README.md | 3 +++ doc/foot.1.scd | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e95fead6..64fe3012 100644 --- a/README.md +++ b/README.md @@ -266,6 +266,9 @@ when starting your Wayland compositor (i.e. logging in to your desktop), and then run `footclient` instead of `foot` whenever you want to launch a new terminal. +Foot support socket activation, which means `foot --server` will only be +started the first time you'll run `footclient`. (systemd user units are +included, but it can work with other supervision suites). ## URLs diff --git a/doc/foot.1.scd b/doc/foot.1.scd index e990ca0d..65d7f5da 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -81,7 +81,7 @@ the foot command line Initial working directory for the client application. Default: _CWD of foot_. -*-s*,*--server*[=_PATH_] +*-s*,*--server*[=_PATH_|_FD_] Run as a server. In this mode, a single foot instance hosts multiple terminals (windows). Use *footclient*(1) to launch new terminals. @@ -116,6 +116,19 @@ the foot command line *--server-socket* option in *footclient*(1) and point it to your custom socket path. + If the argument is a number, foot will interpret it as the file descriptor + of a socket provided by a supervision daemon (such as systemd or s6), and + use that socket as it's own. + + Two systemd units (foot-server@.{service,socket}) are provided to use that + feature with systemd. They need to be instantiated with the value of + $WAYLAND_DISPLAY (multiples instances can co-exists). + + Note that starting *foot --server* as a systemd service will use + the environment of the systemd user instance; thus, if you need specific + environment variables, you'll need to import them using *systemctl --user + import-environment* or use a drop-in for the foot-server service. + *-H*,*--hold* Remain open after child process exits. -- 2.30.2 From 0b7f06bad47eec13e7769c64fc7846963230c2c5 Mon Sep 17 00:00:00 2001 From: Max Gautier Date: Sat, 15 Jan 2022 22:58:19 +0100 Subject: [PATCH 4/5] Socket activation changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62e8a367..94e2f491 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,7 +44,7 @@ * OSC-22 - set xcursor pointer. * Add "xterm" as fallback cursor where "text" is not available. * `[key-bindings].scrollback-home|end` options. - +* Socket activation for `foot --server` and accompanying systemd unit files ### Changed -- 2.30.2 From 522f4e522e9fa392779ee8a7058073c4522b9486 Mon Sep 17 00:00:00 2001 From: Max Gautier Date: Sat, 26 Feb 2022 16:08:57 +0100 Subject: [PATCH 5/5] Install notes regarding systemd unit files --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index 164c129a..40935072 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -56,7 +56,6 @@ following **optional** dependencies: * xdg-utils: URLs are by default launched with `xdg-open`. * bash-completion: If you want completion for positional arguments. - ### Building In addition to the dev variant of the packages above, you need: @@ -68,6 +67,7 @@ In addition to the dev variant of the packages above, you need: * scdoc (for man page generation, not needed if documentation is disabled) * llvm (for PGO builds with Clang) * [tllist](https://codeberg.org/dnkl/tllist) [^1] +* systemd (optional, foot will install systemd unit files if detected) A note on compilers; in general, foot runs **much** faster when compiled with gcc instead of clang. A profile-guided gcc build can be -- 2.30.2