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.
This commit is contained in:
Max Gautier 2022-01-15 16:58:32 +01:00
parent bd5576825f
commit 88a0f7397c

119
server.c
View file

@ -1,6 +1,7 @@
#include "server.h"
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
@ -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;