From 88a0f7397c5c72f3a6a70badff8e3c3c792aeb4f Mon Sep 17 00:00:00 2001 From: Max Gautier Date: Sat, 15 Jan 2022 16:58:32 +0100 Subject: [PATCH] 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 | 119 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 92 insertions(+), 27 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; - - case CONNECT_SUCCESS: - LOG_ERR("%s is already accepting connections; is 'foot --server' already running", sock_path); - /* FALLTHROUGH */ - - case CONNECT_ERR: - goto err; + 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; + } - unlink(sock_path); + switch (try_connect(sock_path)) { + case CONNECT_FAIL: + break; - struct sockaddr_un addr = {.sun_family = AF_UNIX}; - strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path) - 1); + case CONNECT_SUCCESS: + LOG_ERR("%s is already accepting connections; is 'foot --server' already running", sock_path); + /* FALLTHROUGH */ - if (bind(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0) { - LOG_ERRNO("%s: failed to bind", addr.sun_path); - goto err; - } + case CONNECT_ERR: + goto err; + } - if (listen(fd, 0) < 0) { - LOG_ERRNO("%s: failed to listen", addr.sun_path); - goto err; + unlink(sock_path); + + struct sockaddr_un addr = {.sun_family = AF_UNIX}; + strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path) - 1); + + 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;