aboutsummaryrefslogtreecommitdiff
path: root/seatd
diff options
context:
space:
mode:
Diffstat (limited to 'seatd')
-rw-r--r--seatd/client.c484
-rw-r--r--seatd/poll/basic_poller.c386
-rw-r--r--seatd/poll/poller.c53
-rw-r--r--seatd/seat.c521
-rw-r--r--seatd/seatd.c57
-rw-r--r--seatd/server.c232
6 files changed, 1733 insertions, 0 deletions
diff --git a/seatd/client.c b/seatd/client.c
new file mode 100644
index 0000000..72e652c
--- /dev/null
+++ b/seatd/client.c
@@ -0,0 +1,484 @@
+#define _GNU_SOURCE
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "client.h"
+#include "log.h"
+#include "poller.h"
+#include "protocol.h"
+#include "seat.h"
+#include "server.h"
+#include "terminal.h"
+
+static int get_peer(int fd, pid_t *pid, uid_t *uid, gid_t *gid) {
+ struct ucred cred;
+ socklen_t len = sizeof cred;
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) {
+ return -1;
+ }
+ *pid = cred.pid;
+ *uid = cred.uid;
+ *gid = cred.gid;
+ return 0;
+}
+
+struct client *client_create(struct server *server, int client_fd) {
+ uid_t uid;
+ gid_t gid;
+ pid_t pid;
+
+ if (get_peer(client_fd, &pid, &uid, &gid) == -1) {
+ return NULL;
+ }
+
+ struct client *client = calloc(1, sizeof(struct client));
+ if (client == NULL) {
+ return NULL;
+ }
+
+ client->uid = uid;
+ client->gid = gid;
+ client->pid = pid;
+ client->server = server;
+ client->connection.fd = client_fd;
+ list_init(&client->devices);
+ return client;
+}
+
+void client_kill(struct client *client) {
+ assert(client);
+ if (client->connection.fd != -1) {
+ shutdown(client->connection.fd, SHUT_RDWR);
+ close(client->connection.fd);
+ client->connection.fd = -1;
+ };
+ if (client->seat != NULL) {
+ seat_remove_client(client->seat, client);
+ client->seat = NULL;
+ }
+}
+
+void client_destroy(struct client *client) {
+ assert(client);
+ client->server = NULL;
+ if (client->seat != NULL) {
+ // This should also close and remove all devices
+ seat_remove_client(client->seat, client);
+ client->seat = NULL;
+ }
+ if (client->event_source != NULL) {
+ event_source_fd_destroy(client->event_source);
+ client->event_source = NULL;
+ }
+ if (client->connection.fd != -1) {
+ shutdown(client->connection.fd, SHUT_RDWR);
+ close(client->connection.fd);
+ client->connection.fd = -1;
+ }
+ connection_close_fds(&client->connection);
+ assert(client->devices.length == 0);
+ list_free(&client->devices);
+ free(client);
+}
+
+static int client_flush(struct client *client) {
+ int ret = connection_flush(&client->connection);
+ if (ret == -1 && errno == EAGAIN) {
+ event_source_fd_update(client->event_source, EVENT_READABLE | EVENT_WRITABLE);
+ } else if (ret == -1) {
+ return -1;
+ }
+ return 0;
+}
+
+static int client_send_error(struct client *client, int error_code) {
+ struct proto_server_error errmsg = {
+ .error_code = error_code,
+ };
+ struct proto_header errheader = {
+ .opcode = SERVER_ERROR,
+ .size = sizeof errmsg,
+ };
+
+ if (connection_put(&client->connection, &errheader, sizeof errheader) == -1 ||
+ connection_put(&client->connection, &errmsg, sizeof errmsg)) {
+ log_error("could not send error to client");
+ return -1;
+ }
+ return 0;
+}
+
+static char *client_get_seat_name(struct client *client) {
+ (void)client;
+ // TODO: Look up seat for session.
+ return "seat0";
+}
+
+static int handle_open_seat(struct client *client) {
+ char *seat_name = client_get_seat_name(client);
+ if (seat_name == NULL) {
+ log_error("could not get name of target seat");
+ return -1;
+ }
+
+ struct seat *seat = server_get_seat(client->server, seat_name);
+ if (seat == NULL) {
+ log_error("unable to find seat by name");
+ return -1;
+ }
+
+ if (seat_add_client(seat, client) == -1) {
+ log_errorf("unable to add client to target seat: %s", strerror(errno));
+ return -1;
+ }
+
+ size_t seat_name_len = strlen(seat_name);
+
+ struct proto_server_seat_opened rmsg = {
+ .seat_name_len = (uint16_t)seat_name_len,
+ };
+ struct proto_header header = {
+ .opcode = SERVER_SEAT_OPENED,
+ .size = sizeof rmsg + seat_name_len,
+ };
+
+ if (connection_put(&client->connection, &header, sizeof header) == -1 ||
+ connection_put(&client->connection, &rmsg, sizeof rmsg) == -1 ||
+ connection_put(&client->connection, seat_name, seat_name_len) == -1) {
+ log_errorf("unable to write response: %s", strerror(errno));
+ return -1;
+ }
+
+ seat_open_client(seat, client);
+ return 0;
+}
+
+static int handle_close_seat(struct client *client) {
+ if (client->seat == NULL) {
+ log_error("protocol error: no seat associated with client");
+ return -1;
+ }
+
+ if (seat_remove_client(client->seat, client) == -1) {
+ log_error("unable to remove client from seat");
+ return -1;
+ }
+
+ struct proto_header header = {
+ .opcode = SERVER_SEAT_CLOSED,
+ .size = 0,
+ };
+
+ if (connection_put(&client->connection, &header, sizeof header) == -1) {
+ log_errorf("unable to write response: %s", strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int handle_open_device(struct client *client, char *path) {
+ if (client->seat == NULL) {
+ log_error("protocol error: no seat associated with client");
+ return -1;
+ }
+
+ struct seat_device *device = seat_open_device(client, path);
+ if (device == NULL) {
+ log_errorf("could not open device: %s", strerror(errno));
+ goto fail;
+ }
+
+ int dupfd = dup(device->fd);
+ if (dupfd == -1) {
+ log_errorf("could not dup fd: %s", strerror(errno));
+ seat_close_device(client, device);
+ goto fail;
+ }
+
+ if (connection_put_fd(&client->connection, dupfd) == -1) {
+ log_errorf("unable to queue fd for sending: %s", strerror(errno));
+ return -1;
+ }
+
+ struct proto_server_device_opened msg = {
+ .device_id = device->device_id,
+ };
+ struct proto_header header = {
+ .opcode = SERVER_DEVICE_OPENED,
+ .size = sizeof msg,
+ };
+
+ if (connection_put(&client->connection, &header, sizeof header) == -1 ||
+ connection_put(&client->connection, &msg, sizeof msg)) {
+ log_errorf("unable to write response: %s", strerror(errno));
+ return -1;
+ }
+
+ return 0;
+
+fail:
+ return client_send_error(client, errno);
+}
+
+static int handle_close_device(struct client *client, int device_id) {
+ if (client->seat == NULL) {
+ log_error("protocol error: no seat associated with client");
+ return -1;
+ }
+
+ struct seat_device *device = seat_find_device(client, device_id);
+ if (device == NULL) {
+ log_error("no such device");
+ errno = EBADF;
+ goto fail;
+ }
+
+ if (seat_close_device(client, device) == -1) {
+ log_errorf("could not close device: %s", strerror(errno));
+ goto fail;
+ }
+
+ struct proto_server_device_closed msg = {
+ .device_id = device_id,
+ };
+ struct proto_header header = {
+ .opcode = SERVER_DEVICE_CLOSED,
+ .size = sizeof msg,
+ };
+
+ if (connection_put(&client->connection, &header, sizeof header) == -1 ||
+ connection_put(&client->connection, &msg, sizeof msg)) {
+ log_errorf("unable to write response: %s", strerror(errno));
+ return -1;
+ }
+
+ return 0;
+
+fail:
+ return client_send_error(client, errno);
+}
+
+static int handle_switch_session(struct client *client, int session) {
+ if (client->seat == NULL) {
+ log_error("protocol error: no seat associated with client");
+ return -1;
+ }
+
+ struct seat *seat = client->seat;
+ if (seat->active_client != client) {
+ log_info("refusing to switch session: client requesting switch is not active");
+ errno = EPERM;
+ goto error;
+ }
+ if (session <= 0) {
+ log_errorf("invalid session: %d", session);
+ errno = EINVAL;
+ goto error;
+ }
+
+ if (client_get_session(client) == session) {
+ return 0;
+ }
+
+ if (seat_set_next_session(seat, session) == -1) {
+ log_infof("could not queue session switch: %s", strerror(errno));
+ goto error;
+ }
+
+ return 0;
+
+error:
+ return client_send_error(client, errno);
+}
+
+static int handle_disable_seat(struct client *client) {
+ if (client->seat == NULL) {
+ log_error("protocol error: no seat associated with client");
+ return -1;
+ }
+
+ struct seat *seat = client->seat;
+ if (seat->active_client != client) {
+ log_info("client is not currently active");
+ errno = EPERM;
+ goto error;
+ }
+
+ if (seat_close_client(seat, client) == -1) {
+ goto error;
+ }
+
+ return 0;
+
+error:
+ return client_send_error(client, errno);
+}
+
+static int client_handle_opcode(struct client *client, uint16_t opcode, size_t size) {
+ int res = 0;
+ switch (opcode) {
+ case CLIENT_OPEN_SEAT: {
+ if (size != 0) {
+ log_error("protocol error: invalid open_seat message");
+ return -1;
+ }
+ res = handle_open_seat(client);
+ break;
+ }
+ case CLIENT_CLOSE_SEAT: {
+ if (size != 0) {
+ log_error("protocol error: invalid close_seat message");
+ return -1;
+ }
+ res = handle_close_seat(client);
+ break;
+ }
+ case CLIENT_OPEN_DEVICE: {
+ char path[MAX_PATH_LEN];
+ struct proto_client_open_device msg;
+ if (sizeof msg > size || connection_get(&client->connection, &msg, sizeof msg) == -1 ||
+ sizeof msg + msg.path_len > size || msg.path_len > MAX_PATH_LEN) {
+ log_error("protocol error: invalid open_device message");
+ return -1;
+ }
+ if (connection_get(&client->connection, path, msg.path_len) == -1) {
+ log_error("protocol error: invalid open_device message");
+ return -1;
+ }
+
+ res = handle_open_device(client, path);
+ break;
+ }
+ case CLIENT_CLOSE_DEVICE: {
+ struct proto_client_close_device msg;
+ if (sizeof msg > size || connection_get(&client->connection, &msg, sizeof msg) == -1) {
+ log_error("protocol error: invalid close_device message");
+ return -1;
+ }
+
+ res = handle_close_device(client, msg.device_id);
+ break;
+ }
+ case CLIENT_SWITCH_SESSION: {
+ struct proto_client_switch_session msg;
+ if (sizeof msg > size || connection_get(&client->connection, &msg, sizeof msg) == -1) {
+ log_error("protocol error: invalid switch_session message");
+ return -1;
+ }
+
+ res = handle_switch_session(client, msg.session);
+ break;
+ }
+ case CLIENT_DISABLE_SEAT: {
+ if (size != 0) {
+ log_error("protocol error: invalid disable_seat message");
+ return -1;
+ }
+ res = handle_disable_seat(client);
+ break;
+ }
+ default:
+ log_errorf("protocol error: unknown opcode: %d", opcode);
+ res = -1;
+ break;
+ }
+ if (res != -1) {
+ res = client_flush(client);
+ }
+ return res;
+}
+
+int client_disable_seat(struct client *client) {
+ struct proto_header header = {
+ .opcode = SERVER_DISABLE_SEAT,
+ .size = 0,
+ };
+ if (connection_put(&client->connection, &header, sizeof header) == -1 ||
+ connection_flush(&client->connection) == -1) {
+ log_error("unable to send event");
+ return -1;
+ }
+ return 0;
+}
+
+int client_enable_seat(struct client *client) {
+ struct proto_header header = {
+ .opcode = SERVER_ENABLE_SEAT,
+ .size = 0,
+ };
+ if (connection_put(&client->connection, &header, sizeof header) == -1 ||
+ connection_flush(&client->connection) == -1) {
+ log_error("unable to send event");
+ return -1;
+ }
+ return 0;
+}
+
+int client_handle_connection(int fd, uint32_t mask, void *data) {
+ (void)fd;
+
+ struct client *client = data;
+ if (mask & EVENT_ERROR) {
+ log_error("connection error");
+ goto fail;
+ }
+ if (mask & EVENT_HANGUP) {
+ log_info("client disconnected");
+ goto fail;
+ }
+
+ if (mask & EVENT_WRITABLE) {
+ int len = connection_flush(&client->connection);
+ if (len == -1 && errno != EAGAIN) {
+ log_error("could not flush client connection");
+ goto fail;
+ } else if (len >= 0) {
+ event_source_fd_update(client->event_source, EVENT_READABLE);
+ }
+ }
+
+ if (mask & EVENT_READABLE) {
+ int len = connection_read(&client->connection);
+ if (len == 0 || (len == -1 && errno != EAGAIN)) {
+ log_error("could not read client connection");
+ goto fail;
+ }
+
+ struct proto_header header;
+ while (connection_get(&client->connection, &header, sizeof header) != -1) {
+ if (connection_pending(&client->connection) < header.size) {
+ connection_restore(&client->connection, sizeof header);
+ break;
+ }
+ if (client_handle_opcode(client, header.opcode, header.size) == -1) {
+ goto fail;
+ }
+ }
+ }
+
+ return 0;
+
+fail:
+ client_destroy(client);
+ return -1;
+}
+
+int client_get_session(struct client *client) {
+ if (client->seat == NULL || client->seat->active_client != client) {
+ return -1;
+ }
+ if (client->seat->vt_bound) {
+ return client->seat->active_client->seat_vt;
+ }
+ // TODO: Store some session sequence
+ abort();
+ return -1;
+}
diff --git a/seatd/poll/basic_poller.c b/seatd/poll/basic_poller.c
new file mode 100644
index 0000000..a388d8e
--- /dev/null
+++ b/seatd/poll/basic_poller.c
@@ -0,0 +1,386 @@
+#include <assert.h>
+#include <errno.h>
+#include <poll.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "list.h"
+#include "poller.h"
+
+struct basic_poller *global_poller = NULL;
+
+const struct poll_impl basic_poller_impl;
+const struct event_source_fd_impl basic_poller_fd_impl;
+const struct event_source_signal_impl basic_poller_signal_impl;
+
+struct basic_poller {
+ struct poller base;
+ struct list signals;
+ struct list new_signals;
+ struct list fds;
+ struct list new_fds;
+
+ struct pollfd *pollfds;
+ size_t pollfds_len;
+ bool dirty;
+ bool inpoll;
+};
+
+struct basic_poller_fd {
+ struct event_source_fd base;
+ struct basic_poller *poller;
+ bool killed;
+};
+
+struct basic_poller_signal {
+ struct event_source_signal base;
+ struct basic_poller *poller;
+ bool raised;
+ bool killed;
+};
+
+static struct basic_poller *basic_poller_from_poller(struct poller *base) {
+ assert(base->impl == &basic_poller_impl);
+ return (struct basic_poller *)base;
+}
+
+static struct poller *basic_poller_create(void) {
+ if (global_poller != NULL) {
+ errno = EEXIST;
+ return NULL;
+ }
+
+ struct basic_poller *poller = calloc(1, sizeof(struct basic_poller));
+ if (poller == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ list_init(&poller->fds);
+ list_init(&poller->new_fds);
+ list_init(&poller->signals);
+ list_init(&poller->new_signals);
+ poller->base.impl = &basic_poller_impl;
+ global_poller = poller;
+ return (struct poller *)poller;
+}
+
+static int destroy(struct poller *base) {
+ struct basic_poller *poller = basic_poller_from_poller(base);
+ for (size_t idx = 0; idx < poller->fds.length; idx++) {
+ struct basic_poller_fd *bpfd = poller->fds.items[idx];
+ free(bpfd);
+ }
+ list_free(&poller->fds);
+ for (size_t idx = 0; idx < poller->new_fds.length; idx++) {
+ struct basic_poller_fd *bpfd = poller->new_fds.items[idx];
+ free(bpfd);
+ }
+ list_free(&poller->new_fds);
+ for (size_t idx = 0; idx < poller->signals.length; idx++) {
+ struct basic_poller_signal *bps = poller->signals.items[idx];
+
+ struct sigaction sa;
+ sa.sa_handler = SIG_DFL;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction(bps->base.signal, &sa, NULL);
+
+ free(bps);
+ }
+ list_free(&poller->signals);
+ for (size_t idx = 0; idx < poller->new_signals.length; idx++) {
+ struct basic_poller_signal *bps = poller->new_signals.items[idx];
+
+ struct sigaction sa;
+ sa.sa_handler = SIG_DFL;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction(bps->base.signal, &sa, NULL);
+
+ free(bps);
+ }
+ list_free(&poller->new_signals);
+ free(poller->pollfds);
+ return 0;
+}
+
+static int event_mask_to_poll_mask(uint32_t event_mask) {
+ int poll_mask = 0;
+ if (event_mask & EVENT_READABLE) {
+ poll_mask |= POLLIN;
+ }
+ if (event_mask & EVENT_WRITABLE) {
+ poll_mask |= POLLOUT;
+ }
+ return poll_mask;
+}
+
+static uint32_t poll_mask_to_event_mask(int poll_mask) {
+ uint32_t event_mask = 0;
+ if (poll_mask & POLLIN) {
+ event_mask |= EVENT_READABLE;
+ }
+ if (poll_mask & POLLOUT) {
+ event_mask |= EVENT_WRITABLE;
+ }
+ if (poll_mask & POLLERR) {
+ event_mask |= EVENT_ERROR;
+ }
+ if (poll_mask & POLLHUP) {
+ event_mask |= EVENT_HANGUP;
+ }
+ return event_mask;
+}
+
+static int regenerate_pollfds(struct basic_poller *poller) {
+ if (poller->pollfds_len != poller->fds.length) {
+ struct pollfd *fds = calloc(poller->fds.length, sizeof(struct pollfd));
+ if (fds == NULL) {
+ return -1;
+ }
+ free(poller->pollfds);
+ poller->pollfds = fds;
+ poller->pollfds_len = poller->fds.length;
+ }
+
+ for (size_t idx = 0; idx < poller->fds.length; idx++) {
+ struct basic_poller_fd *bpfd = poller->fds.items[idx];
+ poller->pollfds[idx] = (struct pollfd){
+ .fd = bpfd->base.fd,
+ .events = event_mask_to_poll_mask(bpfd->base.mask),
+ };
+ }
+
+ return 0;
+}
+
+static struct event_source_fd *add_fd(struct poller *base, int fd, uint32_t mask,
+ event_source_fd_func_t func, void *data) {
+ struct basic_poller *poller = basic_poller_from_poller(base);
+
+ struct basic_poller_fd *bpfd = calloc(1, sizeof(struct basic_poller_fd));
+ if (bpfd == NULL) {
+ return NULL;
+ }
+ bpfd->base.impl = &basic_poller_fd_impl;
+ bpfd->base.fd = fd;
+ bpfd->base.mask = mask;
+ bpfd->base.data = data;
+ bpfd->base.func = func;
+ bpfd->poller = poller;
+ poller->dirty = true;
+ if (poller->inpoll) {
+ list_add(&poller->new_fds, bpfd);
+ } else {
+ list_add(&poller->fds, bpfd);
+ regenerate_pollfds(poller);
+ }
+ return (struct event_source_fd *)bpfd;
+}
+
+static int fd_destroy(struct event_source_fd *event_source) {
+ struct basic_poller_fd *bpfd = (struct basic_poller_fd *)event_source;
+ struct basic_poller *poller = bpfd->poller;
+ int idx = list_find(&poller->fds, event_source);
+ if (idx == -1) {
+ return -1;
+ }
+ poller->dirty = true;
+ if (poller->inpoll) {
+ bpfd->killed = true;
+ } else {
+ list_del(&poller->fds, idx);
+ free(bpfd);
+ regenerate_pollfds(poller);
+ }
+ return 0;
+}
+
+static int fd_update(struct event_source_fd *event_source, uint32_t mask) {
+ struct basic_poller_fd *bpfd = (struct basic_poller_fd *)event_source;
+ struct basic_poller *poller = bpfd->poller;
+ event_source->mask = mask;
+
+ poller->dirty = true;
+ if (!poller->inpoll) {
+ regenerate_pollfds(poller);
+ }
+ return 0;
+}
+
+static void signal_handler(int sig) {
+ if (global_poller == NULL) {
+ return;
+ }
+
+ for (size_t idx = 0; idx < global_poller->signals.length; idx++) {
+ struct basic_poller_signal *bps = global_poller->signals.items[idx];
+ if (bps->base.signal == sig) {
+ bps->raised = true;
+ }
+ }
+}
+
+static struct event_source_signal *add_signal(struct poller *base, int signal,
+ event_source_signal_func_t func, void *data) {
+ struct basic_poller *poller = basic_poller_from_poller(base);
+
+ struct basic_poller_signal *bps = calloc(1, sizeof(struct basic_poller_signal));
+ if (bps == NULL) {
+ return NULL;
+ }
+
+ int refcnt = 0;
+ for (size_t idx = 0; idx < poller->signals.length; idx++) {
+ struct basic_poller_signal *bps = poller->signals.items[idx];
+ if (bps->base.signal == signal) {
+ refcnt++;
+ }
+ }
+
+ bps->base.impl = &basic_poller_signal_impl;
+ bps->base.signal = signal;
+ bps->base.data = data;
+ bps->base.func = func;
+ bps->poller = poller;
+
+ if (refcnt == 0) {
+ struct sigaction sa;
+ sa.sa_handler = &signal_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction(signal, &sa, NULL);
+ }
+
+ if (poller->inpoll) {
+ list_add(&poller->new_signals, bps);
+ } else {
+ list_add(&poller->signals, bps);
+ }
+
+ return (struct event_source_signal *)bps;
+}
+
+static int signal_destroy(struct event_source_signal *event_source) {
+ struct basic_poller_signal *bps = (struct basic_poller_signal *)event_source;
+ struct basic_poller *poller = bps->poller;
+
+ int idx = list_find(&poller->signals, event_source);
+ if (idx == -1) {
+ return -1;
+ }
+
+ int refcnt = 0;
+ for (size_t idx = 0; idx < poller->signals.length; idx++) {
+ struct basic_poller_signal *b = poller->signals.items[idx];
+ if (b->base.signal == bps->base.signal) {
+ refcnt++;
+ }
+ }
+
+ if (refcnt == 0) {
+ struct sigaction sa;
+ sa.sa_handler = SIG_DFL;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction(bps->base.signal, &sa, NULL);
+ }
+
+ if (poller->inpoll) {
+ bps->killed = true;
+ } else {
+ list_del(&poller->signals, idx);
+ free(bps);
+ }
+ return 0;
+}
+
+static int basic_poller_poll(struct poller *base) {
+ struct basic_poller *poller = basic_poller_from_poller(base);
+
+ if (poll(poller->pollfds, poller->fds.length, -1) == -1 && errno != EINTR) {
+ return -1;
+ }
+
+ poller->inpoll = true;
+
+ for (size_t idx = 0; idx < poller->fds.length; idx++) {
+ short revents = poller->pollfds[idx].revents;
+ if (revents == 0) {
+ continue;
+ }
+ struct basic_poller_fd *bpfd = poller->fds.items[idx];
+ bpfd->base.func(poller->pollfds[idx].fd, poll_mask_to_event_mask(revents),
+ bpfd->base.data);
+ }
+
+ for (size_t idx = 0; idx < poller->signals.length; idx++) {
+ struct basic_poller_signal *bps = poller->signals.items[idx];
+ if (!bps->raised) {
+ continue;
+ }
+ bps->base.func(bps->base.signal, bps->base.data);
+ bps->raised = false;
+ }
+
+ poller->inpoll = false;
+
+ for (size_t idx = 0; idx < poller->fds.length; idx++) {
+ struct basic_poller_fd *bpfd = poller->fds.items[idx];
+ if (!bpfd->killed) {
+ continue;
+ }
+
+ list_del(&poller->fds, idx);
+ free(bpfd);
+ idx--;
+ }
+
+ for (size_t idx = 0; idx < poller->signals.length; idx++) {
+ struct basic_poller_signal *bps = poller->signals.items[idx];
+ if (!bps->killed) {
+ continue;
+ }
+
+ list_del(&poller->signals, idx);
+ free(bps);
+ idx--;
+ }
+
+ if (poller->new_fds.length > 0) {
+ list_concat(&poller->fds, &poller->new_fds);
+ list_truncate(&poller->new_fds);
+ }
+
+ if (poller->new_signals.length > 0) {
+ list_concat(&poller->signals, &poller->new_signals);
+ list_truncate(&poller->new_signals);
+ }
+
+ if (poller->dirty) {
+ regenerate_pollfds(poller);
+ poller->dirty = false;
+ }
+
+ return 0;
+}
+
+const struct event_source_fd_impl basic_poller_fd_impl = {
+ .update = fd_update,
+ .destroy = fd_destroy,
+};
+
+const struct event_source_signal_impl basic_poller_signal_impl = {
+ .destroy = signal_destroy,
+};
+
+const struct poll_impl basic_poller_impl = {
+ .create = basic_poller_create,
+ .destroy = destroy,
+ .add_fd = add_fd,
+ .add_signal = add_signal,
+ .poll = basic_poller_poll,
+};
diff --git a/seatd/poll/poller.c b/seatd/poll/poller.c
new file mode 100644
index 0000000..db39bc0
--- /dev/null
+++ b/seatd/poll/poller.c
@@ -0,0 +1,53 @@
+#include "poller.h"
+#include <assert.h>
+
+extern const struct poll_impl basic_poller_impl;
+
+struct poller *poller_create(void) {
+ // TODO: Other poll impls
+ return basic_poller_impl.create();
+}
+
+int poller_destroy(struct poller *poller) {
+ assert(poller);
+ assert(poller->impl);
+ return poller->impl->destroy(poller);
+}
+
+struct event_source_fd *poller_add_fd(struct poller *poller, int fd, uint32_t mask,
+ event_source_fd_func_t func, void *data) {
+ assert(poller);
+ assert(poller->impl);
+ return poller->impl->add_fd(poller, fd, mask, func, data);
+}
+
+int event_source_fd_destroy(struct event_source_fd *event_source) {
+ assert(event_source);
+ assert(event_source->impl);
+ return event_source->impl->destroy(event_source);
+}
+
+struct event_source_signal *poller_add_signal(struct poller *poller, int signal,
+ event_source_signal_func_t func, void *data) {
+ assert(poller);
+ assert(poller->impl);
+ return poller->impl->add_signal(poller, signal, func, data);
+}
+
+int event_source_signal_destroy(struct event_source_signal *event_source) {
+ assert(event_source);
+ assert(event_source->impl);
+ return event_source->impl->destroy(event_source);
+}
+
+int event_source_fd_update(struct event_source_fd *event_source, uint32_t mask) {
+ assert(event_source);
+ assert(event_source->impl);
+ return event_source->impl->update(event_source, mask);
+}
+
+int poller_poll(struct poller *poller) {
+ assert(poller);
+ assert(poller->impl);
+ return poller->impl->poll(poller);
+}
diff --git a/seatd/seat.c b/seatd/seat.c
new file mode 100644
index 0000000..ab0e338
--- /dev/null
+++ b/seatd/seat.c
@@ -0,0 +1,521 @@
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "client.h"
+#include "drm.h"
+#include "evdev.h"
+#include "list.h"
+#include "log.h"
+#include "protocol.h"
+#include "seat.h"
+#include "terminal.h"
+
+struct seat *seat_create(const char *seat_name, bool vt_bound) {
+ struct seat *seat = calloc(1, sizeof(struct seat));
+ if (seat == NULL) {
+ return NULL;
+ }
+ list_init(&seat->clients);
+ seat->vt_bound = vt_bound;
+
+ seat->seat_name = strdup(seat_name);
+ if (seat->seat_name == NULL) {
+ free(seat);
+ return NULL;
+ }
+
+ log_debugf("created seat '%s' (vt_bound: %d)", seat_name, vt_bound);
+ return seat;
+}
+
+void seat_destroy(struct seat *seat) {
+ assert(seat);
+ while (seat->clients.length > 0) {
+ struct client *client = seat->clients.items[seat->clients.length - 1];
+ // This will cause the client to remove itself from the seat
+ assert(client->seat);
+ client_kill(client);
+ }
+
+ free(seat->seat_name);
+ free(seat);
+}
+
+int seat_add_client(struct seat *seat, struct client *client) {
+ assert(seat);
+ assert(client);
+
+ if (client->seat != NULL) {
+ log_error("cannot add client: client is already a member of a seat");
+ return -1;
+ }
+
+ if (seat->vt_bound && seat->active_client != NULL) {
+ log_error("cannot add client: seat is vt_bound and an active client already exists");
+ return -1;
+ }
+
+ client->seat = seat;
+
+ list_add(&seat->clients, client);
+ log_debug("added client");
+ return 0;
+}
+
+int seat_remove_client(struct seat *seat, struct client *client) {
+ assert(seat);
+ assert(client);
+ assert(client->seat == seat);
+
+ // We must first remove the client to avoid reactivation
+ bool found = false;
+ for (size_t idx = 0; idx < seat->clients.length; idx++) {
+ struct client *c = seat->clients.items[idx];
+ if (client == c) {
+ list_del(&seat->clients, idx);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ log_debug("client was not on the client list");
+ }
+
+ if (seat->next_client == client) {
+ seat->next_client = NULL;
+ }
+
+ while (client->devices.length > 0) {
+ struct seat_device *device = client->devices.items[client->devices.length - 1];
+ seat_close_device(client, device);
+ }
+
+ if (seat->active_client == client) {
+ seat_close_client(seat, client);
+ }
+
+ client->seat = NULL;
+ log_debug("removed client");
+
+ return found ? -1 : 0;
+}
+
+struct seat_device *seat_find_device(struct client *client, int device_id) {
+ assert(client);
+ assert(client->seat);
+ assert(device_id != 0);
+
+ for (size_t idx = 0; idx < client->devices.length; idx++) {
+ struct seat_device *seat_device = client->devices.items[idx];
+ if (seat_device->device_id == device_id) {
+ return seat_device;
+ }
+ }
+ errno = ENOENT;
+ return NULL;
+}
+
+struct seat_device *seat_open_device(struct client *client, const char *path) {
+ assert(client);
+ assert(client->seat);
+ assert(strlen(path) > 0);
+ struct seat *seat = client->seat;
+
+ if (client != seat->active_client) {
+ errno = EPERM;
+ return NULL;
+ }
+
+ char sanitized_path[MAX_PATH_LEN];
+ if (realpath(path, sanitized_path) == NULL) {
+ log_errorf("invalid path '%s': %s", path, strerror(errno));
+ return NULL;
+ }
+
+ int device_id = 1;
+ for (size_t idx = 0; idx < client->devices.length; idx++) {
+ struct seat_device *device = client->devices.items[idx];
+
+ // If the device already exists, increase the ref count and
+ // return it.
+ if (strcmp(device->path, path) == 0) {
+ device->ref_cnt++;
+ return device;
+ }
+
+ // If the device has a higher id, up our device id
+ if (device->device_id >= device_id) {
+ device_id = device->device_id + 1;
+ }
+ }
+
+ if (client->devices.length >= MAX_SEAT_DEVICES) {
+ log_error("max seat devices exceeded");
+ errno = EMFILE;
+ return NULL;
+ }
+
+ const char *prefix = "/dev/";
+ if (strncmp(prefix, sanitized_path, strlen(prefix)) != 0) {
+ log_errorf("invalid path '%s': expected device in /dev", sanitized_path);
+ errno = ENOENT;
+ return NULL;
+ }
+
+ int fd = open(sanitized_path, O_RDWR | O_NOCTTY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
+ if (fd == -1) {
+ log_errorf("could not open file: %s", strerror(errno));
+ return NULL;
+ }
+
+ struct stat st;
+ if (fstat(fd, &st) == -1) {
+ log_errorf("could not fstat: %s", strerror(errno));
+ close(fd);
+ errno = EACCES;
+ return NULL;
+ }
+
+ if (dev_is_drm(st.st_rdev)) {
+ if (drm_set_master(fd) == -1) {
+ log_debugf("drm_set_master failed: %s", strerror(errno));
+ }
+ } else if (dev_is_evdev(st.st_rdev)) {
+ // Nothing to do here
+ } else {
+ // Not a device type we want to share
+ log_errorf("disallowed device type for '%s': %ld", sanitized_path, st.st_rdev);
+ close(fd);
+ errno = EACCES;
+ return NULL;
+ }
+
+ struct seat_device *device = calloc(1, sizeof(struct seat_device));
+ if (device == NULL) {
+ log_errorf("could not alloc device for '%s': %s", sanitized_path, strerror(errno));
+ close(fd);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ device->path = strdup(sanitized_path);
+ if (device->path == NULL) {
+ log_errorf("could not dup path for '%s': %s", sanitized_path, strerror(errno));
+ close(fd);
+ free(device);
+ return NULL;
+ }
+
+ log_debugf("seat: %p, client: %p, path: '%s', device_id: %d", (void *)seat, (void *)client,
+ path, device_id);
+
+ device->ref_cnt++;
+ device->dev = st.st_rdev;
+ device->fd = fd;
+ device->device_id = device_id;
+ device->active = true;
+ list_add(&client->devices, device);
+ return device;
+}
+
+int seat_close_device(struct client *client, struct seat_device *seat_device) {
+ assert(client);
+ assert(client->seat);
+ assert(seat_device && seat_device->fd > 0);
+
+ // Find the device in our list
+ size_t idx = list_find(&client->devices, seat_device);
+ if (idx == -1UL) {
+ log_error("seat device not registered by client");
+ errno = ENOENT;
+ return -1;
+ }
+
+ log_debugf("seat: %p, client: %p, path: '%s', device_id: %d", (void *)client->seat,
+ (void *)client, seat_device->path, seat_device->device_id);
+
+ seat_device->ref_cnt--;
+ if (seat_device->ref_cnt > 0) {
+ // We still have more references to this device, so leave it be.
+ return 0;
+ }
+
+ // The ref count hit zero, so destroy the device
+ list_del(&client->devices, idx);
+ if (seat_device->active && seat_device->fd != -1) {
+ if (dev_is_drm(seat_device->dev)) {
+ if (drm_drop_master(seat_device->fd) == -1) {
+ log_debugf("drm_drop_master failed: %s", strerror(errno));
+ }
+ } else if (dev_is_evdev(seat_device->dev)) {
+ if (evdev_revoke(seat_device->fd) == -1) {
+ log_debugf("evdev_revoke failed: %s", strerror(errno));
+ }
+ }
+ close(seat_device->fd);
+ seat_device->fd = -1;
+ }
+ free(seat_device->path);
+ free(seat_device);
+ return 0;
+}
+
+static int seat_deactivate_device(struct client *client, struct seat_device *seat_device) {
+ assert(client);
+ assert(client->seat);
+ assert(seat_device && seat_device->fd > 0);
+
+ if (!seat_device->active) {
+ return 0;
+ }
+ if (dev_is_drm(seat_device->dev)) {
+ if (drm_drop_master(seat_device->fd) == -1) {
+ return -1;
+ }
+ } else if (dev_is_evdev(seat_device->dev)) {
+ if (evdev_revoke(seat_device->fd) == -1) {
+ return -1;
+ }
+ } else {
+ errno = EACCES;
+ return -1;
+ }
+ seat_device->active = false;
+ return 0;
+}
+
+static int seat_activate_device(struct client *client, struct seat_device *seat_device) {
+ assert(client);
+ assert(client->seat);
+ assert(seat_device && seat_device->fd > 0);
+
+ if (seat_device->active) {
+ return 0;
+ }
+ if (dev_is_drm(seat_device->dev)) {
+ drm_set_master(seat_device->fd);
+ seat_device->active = true;
+ } else if (dev_is_evdev(seat_device->dev)) {
+ // We can't do anything here
+ errno = EINVAL;
+ return -1;
+ } else {
+ errno = EACCES;
+ return -1;
+ }
+ return 0;
+}
+
+int seat_open_client(struct seat *seat, struct client *client) {
+ assert(seat);
+ assert(client);
+
+ if (seat->vt_bound && client->seat_vt == 0) {
+ client->seat_vt = terminal_current_vt();
+ }
+
+ if (seat->active_client != NULL) {
+ log_error("client already active");
+ errno = EBUSY;
+ return -1;
+ }
+
+ if (seat->vt_bound) {
+ terminal_setup(client->seat_vt);
+ terminal_set_keyboard(client->seat_vt, false);
+ }
+
+ for (size_t idx = 0; idx < client->devices.length; idx++) {
+ struct seat_device *device = client->devices.items[idx];
+ if (seat_activate_device(client, device) == -1) {
+ log_errorf("unable to activate '%s': %s", device->path, strerror(errno));
+ }
+ }
+
+ log_debugf("activated %zd devices", client->devices.length);
+
+ seat->active_client = client;
+ if (client_enable_seat(client) == -1) {
+ seat_remove_client(seat, client);
+ return -1;
+ }
+
+ log_info("client successfully enabled");
+ return 0;
+}
+
+int seat_close_client(struct seat *seat, struct client *client) {
+ assert(seat);
+ assert(client);
+
+ if (seat->active_client != client) {
+ log_error("client not active");
+ errno = EBUSY;
+ return -1;
+ }
+
+ // We *deactivate* all remaining fds. These may later be reactivated.
+ // The reason we cannot just close them is that certain device fds, such
+ // as for DRM, must maintain the exact same file description for their
+ // contexts to remain valid.
+ for (size_t idx = 0; idx < client->devices.length; idx++) {
+ struct seat_device *device = client->devices.items[idx];
+ if (seat_deactivate_device(client, device) == -1) {
+ log_errorf("unable to deactivate '%s': %s", device->path, strerror(errno));
+ }
+ }
+
+ log_debugf("deactivated %zd devices", client->devices.length);
+
+ int vt = seat->active_client->seat_vt;
+ seat->active_client = NULL;
+
+ if (seat->vt_bound) {
+ if (seat->vt_pending_ack) {
+ log_debug("acking pending VT switch");
+ seat->vt_pending_ack = false;
+ terminal_teardown(vt);
+ terminal_ack_switch();
+ return 0;
+ }
+ }
+
+ seat_activate(seat);
+ log_debug("closed client");
+ return 0;
+}
+
+int seat_set_next_session(struct seat *seat, int session) {
+ assert(seat);
+
+ // Check if the session number is valid
+ if (session <= 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ // Check if a switch is already queued
+ if (seat->next_vt > 0 || seat->next_client != NULL) {
+ return 0;
+ }
+
+ struct client *target = NULL;
+ for (size_t idx = 0; idx < seat->clients.length; idx++) {
+ struct client *c = seat->clients.items[idx];
+ if (client_get_session(c) == session) {
+ target = c;
+ break;
+ }
+ }
+
+ if (target != NULL) {
+ log_info("queuing switch to different client");
+ seat->next_client = target;
+ seat->next_vt = 0;
+ } else if (seat->vt_bound) {
+ log_info("queuing switch to different VT");
+ seat->next_vt = session;
+ seat->next_client = NULL;
+ } else {
+ log_error("no valid switch available");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (client_disable_seat(seat->active_client) == -1) {
+ seat_remove_client(seat, seat->active_client);
+ }
+
+ return 0;
+}
+
+int seat_activate(struct seat *seat) {
+ assert(seat);
+
+ // We already have an active client!
+ if (seat->active_client != NULL) {
+ return 0;
+ }
+
+ // If we're asked to do a simple VT switch, do that
+ if (seat->vt_bound && seat->next_vt > 0) {
+ log_info("executing VT switch");
+ terminal_switch_vt(seat->next_vt);
+ seat->next_vt = 0;
+ return 0;
+ }
+
+ int vt = -1;
+ if (seat->vt_bound) {
+ vt = terminal_current_vt();
+ }
+
+ // Try to pick a client for activation
+ struct client *next_client = NULL;
+ if (seat->next_client != NULL) {
+ // A specific client has been requested, use it
+ next_client = seat->next_client;
+ seat->next_client = NULL;
+ } else if (seat->clients.length > 0 && seat->vt_bound) {
+ // No client is requested, try to find an applicable one
+ for (size_t idx = 0; idx < seat->clients.length; idx++) {
+ struct client *client = seat->clients.items[idx];
+ if (client->seat_vt == vt) {
+ next_client = client;
+ break;
+ }
+ }
+ } else if (seat->clients.length > 0) {
+ next_client = seat->clients.items[0];
+ }
+
+ if (next_client == NULL) {
+ // No suitable client found
+ log_info("no client suitable for activation");
+ if (seat->vt_bound) {
+ terminal_teardown(vt);
+ }
+ return -1;
+ }
+
+ log_info("activating next client");
+ if (seat->vt_bound && next_client->seat_vt != vt) {
+ terminal_switch_vt(next_client->seat_vt);
+ }
+
+ return seat_open_client(seat, next_client);
+}
+
+int seat_prepare_vt_switch(struct seat *seat) {
+ assert(seat);
+
+ if (seat->active_client == NULL) {
+ log_info("no active client, performing switch immediately");
+ terminal_ack_switch();
+ return 0;
+ }
+
+ if (seat->vt_pending_ack) {
+ log_info("impatient user, killing session to force pending switch");
+ seat_close_client(seat, seat->active_client);
+ return 0;
+ }
+
+ log_debug("delaying VT switch acknowledgement");
+
+ seat->vt_pending_ack = true;
+ if (client_disable_seat(seat->active_client) == -1) {
+ seat_remove_client(seat, seat->active_client);
+ }
+
+ return 0;
+}
diff --git a/seatd/seatd.c b/seatd/seatd.c
new file mode 100644
index 0000000..cfed341
--- /dev/null
+++ b/seatd/seatd.c
@@ -0,0 +1,57 @@
+#include <errno.h>
+#include <poll.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "client.h"
+#include "log.h"
+#include "poller.h"
+#include "server.h"
+
+int main(int argc, char *argv[]) {
+ (void)argc;
+ (void)argv;
+
+ char *loglevel = getenv("SEATD_LOGLEVEL");
+ enum libseat_log_level level = LIBSEAT_ERROR;
+ if (loglevel != NULL) {
+ if (strcmp(loglevel, "silent") == 0) {
+ level = LIBSEAT_SILENT;
+ } else if (strcmp(loglevel, "info") == 0) {
+ level = LIBSEAT_INFO;
+ } else if (strcmp(loglevel, "debug") == 0) {
+ level = LIBSEAT_DEBUG;
+ }
+ }
+ libseat_log_init(level);
+
+ struct server *server = server_create();
+ if (server == NULL) {
+ log_errorf("server_create failed: %s", strerror(errno));
+ return 1;
+ }
+ char *path = getenv("SEATD_SOCK");
+ if (path == NULL) {
+ path = "/run/seatd.sock";
+ }
+
+ if (server_listen(server, path) == -1) {
+ log_errorf("server_listen failed: %s", strerror(errno));
+ return 1;
+ }
+
+ log_info("seatd started");
+
+ while (server->running) {
+ if (poller_poll(server->poller) == -1) {
+ log_errorf("poller failed: %s", strerror(errno));
+ return 1;
+ }
+ }
+ unlink(path);
+ return 0;
+}
diff --git a/seatd/server.c b/seatd/server.c
new file mode 100644
index 0000000..3db9b69
--- /dev/null
+++ b/seatd/server.c
@@ -0,0 +1,232 @@
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "client.h"
+#include "list.h"
+#include "log.h"
+#include "poller.h"
+#include "seat.h"
+#include "server.h"
+#include "terminal.h"
+
+#define LISTEN_BACKLOG 16
+
+static int server_handle_vt_acq(int signal, void *data);
+static int server_handle_vt_rel(int signal, void *data);
+static int server_handle_kill(int signal, void *data);
+
+struct server *server_create(void) {
+ struct poller *poller = poller_create();
+ if (poller == NULL) {
+ log_error("could not create poller");
+ return NULL;
+ }
+ struct server *server = calloc(1, sizeof(struct server));
+ if (server == NULL) {
+ return NULL;
+ }
+ server->poller = poller;
+
+ list_init(&server->seats);
+
+ if (poller_add_signal(poller, SIGUSR1, server_handle_vt_rel, server) == NULL ||
+ poller_add_signal(poller, SIGUSR2, server_handle_vt_acq, server) == NULL ||
+ poller_add_signal(poller, SIGINT, server_handle_kill, server) == NULL ||
+ poller_add_signal(poller, SIGTERM, server_handle_kill, server) == NULL) {
+ server_destroy(server);
+ return NULL;
+ }
+
+ char *vtenv = getenv("SEATD_VTBOUND");
+
+ // TODO: create more seats:
+ struct seat *seat = seat_create("seat0", vtenv == NULL || strcmp(vtenv, "1") == 0);
+ if (seat == NULL) {
+ server_destroy(server);
+ return NULL;
+ }
+
+ list_add(&server->seats, seat);
+ server->running = true;
+ return server;
+}
+
+void server_destroy(struct server *server) {
+ assert(server);
+ for (size_t idx = 0; idx < server->seats.length; idx++) {
+ struct seat *seat = server->seats.items[idx];
+ seat_destroy(seat);
+ }
+ list_free(&server->seats);
+ if (server->poller != NULL) {
+ poller_destroy(server->poller);
+ server->poller = NULL;
+ }
+ free(server);
+}
+
+struct seat *server_get_seat(struct server *server, const char *seat_name) {
+ for (size_t idx = 0; idx < server->seats.length; idx++) {
+ struct seat *seat = server->seats.items[idx];
+ if (strcmp(seat->seat_name, seat_name) == 0) {
+ return seat;
+ }
+ }
+ return NULL;
+}
+
+static int server_handle_vt_acq(int signal, void *data) {
+ (void)signal;
+ struct server *server = data;
+ struct seat *seat = server_get_seat(server, "seat0");
+ if (seat == NULL) {
+ return -1;
+ }
+
+ seat_activate(seat);
+ return 0;
+}
+
+static int server_handle_vt_rel(int signal, void *data) {
+ (void)signal;
+ struct server *server = data;
+ struct seat *seat = server_get_seat(server, "seat0");
+ if (seat == NULL) {
+ return -1;
+ }
+
+ seat_prepare_vt_switch(seat);
+ return 0;
+}
+
+static int server_handle_kill(int signal, void *data) {
+ (void)signal;
+ struct server *server = data;
+ server->running = false;
+ return 0;
+}
+
+static int set_nonblock(int fd) {
+ int flags;
+ if ((flags = fcntl(fd, F_GETFD)) == -1 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
+ log_errorf("could not set FD_CLOEXEC on socket: %s", strerror(errno));
+ return -1;
+ }
+ if ((flags = fcntl(fd, F_GETFL)) == -1 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+ log_errorf("could not set O_NONBLOCK on socket: %s", strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+static int server_handle_connection(int fd, uint32_t mask, void *data) {
+ struct server *server = data;
+ if (mask & (EVENT_ERROR | EVENT_HANGUP)) {
+ close(fd);
+ log_errorf("server socket recieved an error: %s", strerror(errno));
+ exit(1);
+ }
+
+ if (mask & EVENT_READABLE) {
+ int new_fd = accept(fd, NULL, NULL);
+ if (fd == -1) {
+ log_errorf("could not accept client connection: %s", strerror(errno));
+ return 0;
+ }
+
+ if (set_nonblock(new_fd) != 0) {
+ close(new_fd);
+ log_errorf("could not prepare new client socket: %s", strerror(errno));
+ return 0;
+ }
+
+ struct client *client = client_create(server, new_fd);
+ client->event_source = poller_add_fd(server->poller, new_fd, EVENT_READABLE,
+ client_handle_connection, client);
+ if (client->event_source == NULL) {
+ client_destroy(client);
+ log_errorf("could not add client socket to poller: %s", strerror(errno));
+ return 0;
+ }
+ log_infof("new client connected (pid: %d, uid: %d, gid: %d)", client->pid,
+ client->uid, client->gid);
+ }
+ return 0;
+}
+
+int server_add_client(struct server *server, int fd) {
+ if (set_nonblock(fd) != 0) {
+ close(fd);
+ log_errorf("could not prepare new client socket: %s", strerror(errno));
+ return -1;
+ }
+
+ struct client *client = client_create(server, fd);
+ client->event_source =
+ poller_add_fd(server->poller, fd, EVENT_READABLE, client_handle_connection, client);
+ if (client->event_source == NULL) {
+ client_destroy(client);
+ log_errorf("could not add client socket to poller: %s", strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+int server_listen(struct server *server, const char *path) {
+ union {
+ struct sockaddr_un unix;
+ struct sockaddr generic;
+ } addr = {0};
+ int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1) {
+ log_errorf("could not create socket: %s", strerror(errno));
+ return -1;
+ }
+ if (set_nonblock(fd) == -1) {
+ close(fd);
+ log_errorf("could not prepare socket: %s", strerror(errno));
+ return -1;
+ }
+
+ addr.unix.sun_family = AF_UNIX;
+ strncpy(addr.unix.sun_path, path, sizeof addr.unix.sun_path - 1);
+ socklen_t size = offsetof(struct sockaddr_un, sun_path) + strlen(addr.unix.sun_path);
+ if (bind(fd, &addr.generic, size) == -1) {
+ log_errorf("could not bind socket: %s", strerror(errno));
+ close(fd);
+ return -1;
+ }
+ if (listen(fd, LISTEN_BACKLOG) == -1) {
+ log_errorf("could not listen on socket: %s", strerror(errno));
+ close(fd);
+ return -1;
+ }
+ struct group *videogrp = getgrnam("video");
+ if (videogrp != NULL) {
+ if (chown(path, 0, videogrp->gr_gid) == -1) {
+ log_errorf("could not chown socket to video group: %s", strerror(errno));
+ } else if (chmod(path, 0770) == -1) {
+ log_errorf("could not chmod socket: %s", strerror(errno));
+ }
+ } else {
+ log_errorf("could not get video group: %s", strerror(errno));
+ }
+ if (poller_add_fd(server->poller, fd, EVENT_READABLE, server_handle_connection, server) ==
+ NULL) {
+ log_errorf("could not add socket to poller: %s", strerror(errno));
+ close(fd);
+ return -1;
+ }
+ return 0;
+}