aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKenny Levinsen <kl@kl.wtf>2020-07-31 00:22:18 +0200
committerKenny Levinsen <kl@kl.wtf>2020-07-31 00:22:18 +0200
commit61716a2c77dfde9addf6b41a6d72d26a8584150e (patch)
tree537cd84661955497bdb304f88896e36896df4e5f
parentf85434de666f10da0cbcaccdbb7d88917c5fa887 (diff)
Initial implementation of seatd and libseat
-rw-r--r--common/connection.c312
-rw-r--r--common/drm.c23
-rw-r--r--common/evdev.c15
-rw-r--r--common/list.c76
-rw-r--r--common/log.c80
-rw-r--r--common/terminal.c202
-rw-r--r--examples/simpletest/main.c60
-rw-r--r--include/backend.h32
-rw-r--r--include/client.h37
-rw-r--r--include/compiler.h18
-rw-r--r--include/connection.h36
-rw-r--r--include/drm.h10
-rw-r--r--include/evdev.h9
-rw-r--r--include/libseat.h140
-rw-r--r--include/list.h22
-rw-r--r--include/log.h51
-rw-r--r--include/poller.h137
-rw-r--r--include/protocol.h64
-rw-r--r--include/seat.h47
-rw-r--r--include/server.h26
-rw-r--r--include/terminal.h14
-rw-r--r--libseat/backend/logind.c782
-rw-r--r--libseat/backend/seatd.c545
-rw-r--r--libseat/libseat.c111
-rw-r--r--meson.build157
-rw-r--r--meson_options.txt5
-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
32 files changed, 4744 insertions, 0 deletions
diff --git a/common/connection.c b/common/connection.c
new file mode 100644
index 0000000..2545e5d
--- /dev/null
+++ b/common/connection.c
@@ -0,0 +1,312 @@
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "compiler.h"
+#include "connection.h"
+
+#define CLEN (CMSG_LEN(MAX_FDS_OUT * sizeof(int)))
+
+ALWAYS_INLINE static uint32_t connection_buffer_mask(const uint32_t idx) {
+ return idx & (CONNECTION_BUFFER_SIZE - 1);
+}
+
+ALWAYS_INLINE static uint32_t connection_buffer_size(const struct connection_buffer *b) {
+ return b->head - b->tail;
+}
+
+ALWAYS_INLINE static void connection_buffer_consume(struct connection_buffer *b, const size_t size) {
+ b->tail += size;
+}
+
+ALWAYS_INLINE static void connection_buffer_restore(struct connection_buffer *b, const size_t size) {
+ b->tail -= size;
+}
+
+/*
+ * connection_buffer_get_iov prepares I/O vectors pointing to our ring buffer.
+ * Two may be used if the buffer has wrapped around.
+ */
+static void connection_buffer_get_iov(struct connection_buffer *b, struct iovec *iov, int *count) {
+ uint32_t head = connection_buffer_mask(b->head);
+ uint32_t tail = connection_buffer_mask(b->tail);
+ if (tail < head) {
+ iov[0].iov_base = b->data + tail;
+ iov[0].iov_len = head - tail;
+ *count = 1;
+ } else if (head == 0) {
+ iov[0].iov_base = b->data + tail;
+ iov[0].iov_len = sizeof b->data - tail;
+ *count = 1;
+ } else {
+ iov[0].iov_base = b->data + tail;
+ iov[0].iov_len = sizeof b->data - tail;
+ iov[1].iov_base = b->data;
+ iov[1].iov_len = head;
+ *count = 2;
+ }
+}
+
+/*
+ * connection_buffer_put_iov prepares I/O vectors pointing to our ring buffer.
+ * Two may be used if the buffer has wrapped around.
+ */
+static void connection_buffer_put_iov(struct connection_buffer *b, struct iovec *iov, int *count) {
+ uint32_t head = connection_buffer_mask(b->head);
+ uint32_t tail = connection_buffer_mask(b->tail);
+ if (head < tail) {
+ iov[0].iov_base = b->data + head;
+ iov[0].iov_len = tail - head;
+ *count = 1;
+ } else if (tail == 0) {
+ iov[0].iov_base = b->data + head;
+ iov[0].iov_len = sizeof b->data - head;
+ *count = 1;
+ } else {
+ iov[0].iov_base = b->data + head;
+ iov[0].iov_len = sizeof b->data - head;
+ iov[1].iov_base = b->data;
+ iov[1].iov_len = tail;
+ *count = 2;
+ }
+}
+
+/*
+ * connection_buffer_copy copies from our ring buffer into a linear buffer.
+ */
+static void connection_buffer_copy(const struct connection_buffer *b, void *data, const size_t count) {
+ uint32_t tail = connection_buffer_mask(b->tail);
+ if (tail + count <= sizeof b->data) {
+ memcpy(data, b->data + tail, count);
+ return;
+ }
+
+ uint32_t size = sizeof b->data - tail;
+ memcpy(data, b->data + tail, size);
+ memcpy((char *)data + size, b->data, count - size);
+}
+/*
+ * connection_buffer_copy copies from a linear buffer into our ring buffer.
+ */
+static int connection_buffer_put(struct connection_buffer *b, const void *data, const size_t count) {
+ if (count > sizeof(b->data)) {
+ errno = EOVERFLOW;
+ return -1;
+ }
+
+ uint32_t head = connection_buffer_mask(b->head);
+ if (head + count <= sizeof b->data) {
+ memcpy(b->data + head, data, count);
+ } else {
+ uint32_t size = sizeof b->data - head;
+ memcpy(b->data + head, data, size);
+ memcpy(b->data, (const char *)data + size, count - size);
+ }
+
+ b->head += count;
+ return 0;
+}
+
+/*
+ * close_fds closes all fds within a connection_buffer
+ */
+static void connection_buffer_close_fds(struct connection_buffer *buffer) {
+ size_t size = connection_buffer_size(buffer);
+ if (size == 0) {
+ return;
+ }
+ int fds[sizeof(buffer->data) / sizeof(int)];
+ connection_buffer_copy(buffer, fds, size);
+ int count = size / sizeof fds[0];
+ size = count * sizeof fds[0];
+ for (int idx = 0; idx < count; idx++) {
+ close(fds[idx]);
+ }
+ connection_buffer_consume(buffer, size);
+}
+
+/*
+ * build_cmsg prepares a cmsg from a buffer full of fds
+ */
+static void build_cmsg(struct connection_buffer *buffer, char *data, int *clen) {
+ size_t size = connection_buffer_size(buffer);
+ if (size > MAX_FDS_OUT * sizeof(int)) {
+ size = MAX_FDS_OUT * sizeof(int);
+ }
+
+ if (size <= 0) {
+ *clen = 0;
+ return;
+ }
+
+ struct cmsghdr *cmsg = (struct cmsghdr *)data;
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(size);
+ connection_buffer_copy(buffer, CMSG_DATA(cmsg), size);
+ *clen = cmsg->cmsg_len;
+}
+
+static int decode_cmsg(struct connection_buffer *buffer, struct msghdr *msg) {
+ bool overflow = false;
+ struct cmsghdr *cmsg;
+ for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) {
+ continue;
+ }
+
+ size_t size = cmsg->cmsg_len - CMSG_LEN(0);
+ size_t max = sizeof(buffer->data) - connection_buffer_size(buffer);
+ if (size > max || overflow) {
+ overflow = true;
+ size /= sizeof(int);
+ for (size_t idx = 0; idx < size; idx++) {
+ close(((int *)CMSG_DATA(cmsg))[idx]);
+ }
+ } else if (connection_buffer_put(buffer, CMSG_DATA(cmsg), size) < 0) {
+ return -1;
+ }
+ }
+
+ if (overflow) {
+ errno = EOVERFLOW;
+ return -1;
+ }
+ return 0;
+}
+
+int connection_read(struct connection *connection) {
+ if (connection_buffer_size(&connection->in) >= sizeof(connection->in.data)) {
+ errno = EOVERFLOW;
+ return -1;
+ }
+
+ int count;
+ struct iovec iov[2];
+ connection_buffer_put_iov(&connection->in, iov, &count);
+
+ char cmsg[CLEN];
+ struct msghdr msg = {
+ .msg_name = NULL,
+ .msg_namelen = 0,
+ .msg_iov = iov,
+ .msg_iovlen = count,
+ .msg_control = cmsg,
+ .msg_controllen = sizeof cmsg,
+ .msg_flags = 0,
+ };
+
+ int len;
+ do {
+ len = recvmsg(connection->fd, &msg, MSG_DONTWAIT | MSG_CMSG_CLOEXEC);
+ if (len == -1 && errno != EINTR)
+ return -1;
+ } while (len == -1);
+
+ if (decode_cmsg(&connection->fds_in, &msg) != 0) {
+ return -1;
+ }
+ connection->in.head += len;
+
+ return connection_buffer_size(&connection->in);
+}
+
+int connection_flush(struct connection *connection) {
+ if (!connection->want_flush) {
+ return 0;
+ }
+
+ uint32_t tail = connection->out.tail;
+ while (connection->out.head - connection->out.tail > 0) {
+ int count;
+ struct iovec iov[2];
+ connection_buffer_get_iov(&connection->out, iov, &count);
+
+ int clen;
+ char cmsg[CLEN];
+ build_cmsg(&connection->fds_out, cmsg, &clen);
+ struct msghdr msg = {
+ .msg_name = NULL,
+ .msg_namelen = 0,
+ .msg_iov = iov,
+ .msg_iovlen = count,
+ .msg_control = (clen > 0) ? cmsg : NULL,
+ .msg_controllen = clen,
+ .msg_flags = 0,
+ };
+
+ int len;
+ do {
+ len = sendmsg(connection->fd, &msg, MSG_NOSIGNAL | MSG_DONTWAIT);
+ if (len == -1 && errno != EINTR)
+ return -1;
+ } while (len == -1);
+ connection_buffer_close_fds(&connection->fds_out);
+ connection->out.tail += len;
+ }
+ connection->want_flush = 0;
+ return connection->out.head - tail;
+}
+
+int connection_put(struct connection *connection, const void *data, size_t count) {
+ if (connection_buffer_size(&connection->out) + count > CONNECTION_BUFFER_SIZE) {
+ connection->want_flush = 1;
+ if (connection_flush(connection) == -1) {
+ return -1;
+ }
+ }
+
+ if (connection_buffer_put(&connection->out, data, count) == -1) {
+ return -1;
+ }
+
+ connection->want_flush = 1;
+ return 0;
+}
+
+int connection_put_fd(struct connection *connection, int fd) {
+ if (connection_buffer_size(&connection->fds_out) == MAX_FDS_OUT * sizeof fd) {
+ errno = EOVERFLOW;
+ return -1;
+ }
+
+ return connection_buffer_put(&connection->fds_out, &fd, sizeof fd);
+}
+
+int connection_get(struct connection *connection, void *dst, size_t count) {
+ if (count > connection_buffer_size(&connection->in)) {
+ errno = EAGAIN;
+ return -1;
+ }
+ connection_buffer_copy(&connection->in, dst, count);
+ connection_buffer_consume(&connection->in, count);
+ return count;
+}
+
+int connection_get_fd(struct connection *connection) {
+ int fd;
+ if (sizeof fd > connection_buffer_size(&connection->fds_in)) {
+ errno = EAGAIN;
+ return -1;
+ }
+ connection_buffer_copy(&connection->fds_in, &fd, sizeof fd);
+ connection_buffer_consume(&connection->fds_in, sizeof fd);
+ return fd;
+}
+
+void connection_close_fds(struct connection *connection) {
+ connection_buffer_close_fds(&connection->fds_in);
+ connection_buffer_close_fds(&connection->fds_out);
+}
+
+size_t connection_pending(struct connection *connection) {
+ return connection_buffer_size(&connection->in);
+}
+
+void connection_restore(struct connection *connection, size_t count) {
+ connection_buffer_restore(&connection->in, count);
+}
diff --git a/common/drm.c b/common/drm.c
new file mode 100644
index 0000000..4b70b64
--- /dev/null
+++ b/common/drm.c
@@ -0,0 +1,23 @@
+#include <sys/ioctl.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+
+#include "drm.h"
+
+// From libdrm
+#define DRM_IOCTL_BASE 'd'
+#define DRM_IO(nr) _IO(DRM_IOCTL_BASE, nr)
+#define DRM_IOCTL_SET_MASTER DRM_IO(0x1e)
+#define DRM_IOCTL_DROP_MASTER DRM_IO(0x1f)
+
+int drm_set_master(int fd) {
+ return ioctl(fd, DRM_IOCTL_SET_MASTER, 0);
+}
+
+int drm_drop_master(int fd) {
+ return ioctl(fd, DRM_IOCTL_DROP_MASTER, 0);
+}
+
+int dev_is_drm(dev_t device) {
+ return major(device) == 226;
+}
diff --git a/common/evdev.c b/common/evdev.c
new file mode 100644
index 0000000..fe6922d
--- /dev/null
+++ b/common/evdev.c
@@ -0,0 +1,15 @@
+#include <linux/input.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+
+#include "evdev.h"
+
+int evdev_revoke(int fd) {
+ return ioctl(fd, EVIOCREVOKE, NULL);
+}
+
+int dev_is_evdev(dev_t device) {
+ return major(device) == 13;
+}
diff --git a/common/list.c b/common/list.c
new file mode 100644
index 0000000..1a86837
--- /dev/null
+++ b/common/list.c
@@ -0,0 +1,76 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "list.h"
+
+void list_init(struct list *list) {
+ list->capacity = 10;
+ list->length = 0;
+ list->items = malloc(sizeof(void *) * list->capacity);
+}
+
+static void list_resize(struct list *list) {
+ if (list->length == list->capacity) {
+ list->capacity *= 2;
+ list->items = realloc(list->items, sizeof(void *) * list->capacity);
+ }
+}
+
+void list_free(struct list *list) {
+ list->capacity = 0;
+ list->length = 0;
+ free(list->items);
+}
+
+void list_add(struct list *list, void *item) {
+ list_resize(list);
+ list->items[list->length++] = item;
+}
+
+void list_insert(struct list *list, size_t index, void *item) {
+ list_resize(list);
+ memmove(&list->items[index + 1], &list->items[index],
+ sizeof(void *) * (list->length - index));
+ list->length++;
+ list->items[index] = item;
+}
+
+void list_del(struct list *list, size_t index) {
+ list->length--;
+ memmove(&list->items[index], &list->items[index + 1],
+ sizeof(void *) * (list->length - index));
+}
+
+size_t list_find(struct list *list, const void *item) {
+ for (size_t i = 0; i < list->length; i++) {
+ if (list->items[i] == item) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+void list_concat(struct list *list, struct list *source) {
+ if (list->length + source->length > list->capacity) {
+ while (list->length + source->length > list->capacity) {
+ list->capacity *= 2;
+ }
+ list->items = realloc(list->items, sizeof(void *) * list->capacity);
+ }
+ memmove(&list->items[list->length], source->items, sizeof(void *) * (source->length));
+ list->length += source->length;
+}
+
+void list_truncate(struct list *list) {
+ list->length = 0;
+}
+
+void *list_pop_front(struct list *list) {
+ if (list->length == 0) {
+ return NULL;
+ }
+ void *item = list->items[0];
+ list_del(list, 0);
+ return item;
+}
diff --git a/common/log.c b/common/log.c
new file mode 100644
index 0000000..1474dcc
--- /dev/null
+++ b/common/log.c
@@ -0,0 +1,80 @@
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "log.h"
+
+const long NSEC_PER_SEC = 1000000000;
+
+static enum libseat_log_level current_log_level;
+static struct timespec start_time = {-1, -1};
+static bool colored = false;
+
+static const char *verbosity_colors[] = {
+ [LIBSEAT_SILENT] = "",
+ [LIBSEAT_ERROR] = "\x1B[1;31m",
+ [LIBSEAT_INFO] = "\x1B[1;34m",
+ [LIBSEAT_DEBUG] = "\x1B[1;90m",
+};
+
+static const char *verbosity_headers[] = {
+ [LIBSEAT_SILENT] = "",
+ [LIBSEAT_ERROR] = "[ERROR]",
+ [LIBSEAT_INFO] = "[INFO]",
+ [LIBSEAT_DEBUG] = "[DEBUG]",
+};
+
+static void timespec_sub(struct timespec *r, const struct timespec *a, const struct timespec *b) {
+ r->tv_sec = a->tv_sec - b->tv_sec;
+ r->tv_nsec = a->tv_nsec - b->tv_nsec;
+ if (r->tv_nsec < 0) {
+ r->tv_sec--;
+ r->tv_nsec += NSEC_PER_SEC;
+ }
+}
+
+void libseat_log_init(enum libseat_log_level level) {
+ if (start_time.tv_sec >= 0) {
+ return;
+ }
+ clock_gettime(CLOCK_MONOTONIC, &start_time);
+ current_log_level = level;
+ colored = isatty(STDERR_FILENO);
+}
+
+void _libseat_logf(enum libseat_log_level level, const char *fmt, ...) {
+ int stored_errno = errno;
+ va_list args;
+ if (level < current_log_level) {
+ return;
+ }
+
+ struct timespec ts = {0};
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ timespec_sub(&ts, &ts, &start_time);
+ unsigned c = (level < LIBSEAT_LOG_LEVEL_LAST) ? level : LIBSEAT_LOG_LEVEL_LAST - 1;
+
+ const char *prefix, *postfix;
+
+ if (colored) {
+ prefix = verbosity_colors[c];
+ postfix = "\x1B[0m\n";
+ } else {
+ prefix = verbosity_headers[c];
+ postfix = "\n";
+ }
+
+ fprintf(stderr, "%02d:%02d:%02d.%03ld %s ", (int)(ts.tv_sec / 60 / 60),
+ (int)(ts.tv_sec / 60 % 60), (int)(ts.tv_sec % 60), ts.tv_nsec / 1000000, prefix);
+
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+
+ fprintf(stderr, "%s", postfix);
+ errno = stored_errno;
+}
diff --git a/common/terminal.c b/common/terminal.c
new file mode 100644
index 0000000..c9ee758
--- /dev/null
+++ b/common/terminal.c
@@ -0,0 +1,202 @@
+#include "string.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/kd.h>
+#include <linux/vt.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "terminal.h"
+
+#define TTYPATHLEN 64
+
+int terminal_current_vt(void) {
+ struct vt_stat st;
+ int fd = open("/dev/tty0", O_RDWR | O_NOCTTY);
+ if (fd == -1) {
+ log_errorf("could not open tty0: %s", strerror(errno));
+ return -1;
+ }
+ int res = ioctl(fd, VT_GETSTATE, &st);
+ close(fd);
+ if (res == -1) {
+ log_errorf("could not retrieve VT state: %s", strerror(errno));
+ return -1;
+ }
+ return st.v_active;
+}
+
+int terminal_setup(int vt) {
+ log_debugf("setting up vt %d", vt);
+ if (vt == -1) {
+ vt = 0;
+ }
+ char path[TTYPATHLEN];
+ if (snprintf(path, TTYPATHLEN, "/dev/tty%d", vt) == -1) {
+ log_errorf("could not generate tty path: %s", strerror(errno));
+ return -1;
+ }
+ int fd = open(path, O_RDWR | O_NOCTTY);
+ if (fd == -1) {
+ log_errorf("could not open target tty: %s", strerror(errno));
+ return -1;
+ }
+
+ static struct vt_mode mode = {
+ .mode = VT_PROCESS,
+ .waitv = 0,
+ .relsig = SIGUSR1,
+ .acqsig = SIGUSR2,
+ .frsig = 0,
+ };
+
+ int res = ioctl(fd, VT_SETMODE, &mode);
+ close(fd);
+ if (res == -1) {
+ log_errorf("could not set VT mode: %s", strerror(errno));
+ }
+
+ return res;
+}
+
+int terminal_teardown(int vt) {
+ log_debugf("tearing down vt %d", vt);
+ if (vt == -1) {
+ vt = 0;
+ }
+ char path[TTYPATHLEN];
+ if (snprintf(path, TTYPATHLEN, "/dev/tty%d", vt) == -1) {
+ log_errorf("could not generate tty path: %s", strerror(errno));
+ return -1;
+ }
+ int fd = open(path, O_RDWR | O_NOCTTY);
+ if (fd == -1) {
+ log_errorf("could not open target tty: %s", strerror(errno));
+ return -1;
+ }
+ if (ioctl(fd, KDSETMODE, KD_TEXT) == -1) {
+ log_errorf("could not set KD graphics mode: %s", strerror(errno));
+ close(fd);
+ return -1;
+ }
+ if (ioctl(fd, KDSKBMODE, K_UNICODE) == -1) {
+ log_errorf("could not set KD keyboard mode: %s", strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ static struct vt_mode mode = {
+ .mode = VT_PROCESS,
+ .waitv = 0,
+ .relsig = SIGUSR1,
+ .acqsig = SIGUSR2,
+ .frsig = 0,
+ };
+ if (ioctl(fd, VT_SETMODE, &mode) == -1) {
+ log_errorf("could not set VT mode: %s", strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+ return 0;
+}
+
+int terminal_switch_vt(int vt) {
+ log_debugf("switching to vt %d", vt);
+ int fd = open("/dev/tty0", O_RDWR | O_NOCTTY);
+ if (fd == -1) {
+ log_errorf("could not open tty0: %s", strerror(errno));
+ return -1;
+ }
+
+ static struct vt_mode mode = {
+ .mode = VT_PROCESS,
+ .waitv = 0,
+ .relsig = SIGUSR1,
+ .acqsig = SIGUSR2,
+ .frsig = 0,
+ };
+
+ if (ioctl(fd, VT_SETMODE, &mode) == -1) {
+ log_errorf("could not set VT mode: %s", strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ if (ioctl(fd, VT_ACTIVATE, vt) == -1) {
+ log_errorf("could not activate VT: %s", strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+ return 0;
+}
+
+int terminal_ack_switch(void) {
+ log_debug("acking vt switch");
+ int fd = open("/dev/tty0", O_RDWR | O_NOCTTY);
+ if (fd == -1) {
+ log_errorf("could not open tty0: %s", strerror(errno));
+ return -1;
+ }
+
+ int res = ioctl(fd, VT_RELDISP, VT_ACKACQ);
+ close(fd);
+ if (res == -1) {
+ log_errorf("could not ack VT switch: %s", strerror(errno));
+ }
+
+ return res;
+}
+
+int terminal_set_keyboard(int vt, bool enable) {
+ log_debugf("setting KD keyboard state to %d on vt %d", enable, vt);
+ if (vt == -1) {
+ vt = 0;
+ }
+ char path[TTYPATHLEN];
+ if (snprintf(path, TTYPATHLEN, "/dev/tty%d", vt) == -1) {
+ log_errorf("could not generate tty path: %s", strerror(errno));
+ return -1;
+ }
+ int fd = open(path, O_RDWR | O_NOCTTY);
+ if (fd == -1) {
+ log_errorf("could not generate tty path: %s", strerror(errno));
+ return -1;
+ }
+ int res = ioctl(fd, KDSKBMODE, enable ? K_UNICODE : K_OFF);
+ close(fd);
+ if (res == -1) {
+ log_errorf("could not set KD keyboard mode: %s", strerror(errno));
+ }
+ return res;
+}
+
+int terminal_set_graphics(int vt, bool enable) {
+ log_debugf("setting KD graphics state to %d on vt %d", enable, vt);
+ if (vt == -1) {
+ vt = 0;
+ }
+ char path[TTYPATHLEN];
+ if (snprintf(path, TTYPATHLEN, "/dev/tty%d", vt) == -1) {
+ log_errorf("could not generate tty path: %s", strerror(errno));
+ return -1;
+ }
+ int fd = open(path, O_RDWR | O_NOCTTY);
+ if (fd == -1) {
+ log_errorf("could not generate tty path: %s", strerror(errno));
+ return -1;
+ }
+ int res = ioctl(fd, KDSETMODE, enable ? KD_GRAPHICS : KD_TEXT);
+ close(fd);
+ if (res == -1) {
+ log_errorf("could not set KD graphics mode: %s", strerror(errno));
+ }
+ return res;
+}
diff --git a/examples/simpletest/main.c b/examples/simpletest/main.c
new file mode 100644
index 0000000..cd2e243
--- /dev/null
+++ b/examples/simpletest/main.c
@@ -0,0 +1,60 @@
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "libseat.h"
+
+static void handle_enable(struct libseat *backend, void *data) {
+ (void)backend;
+ int *active = (int *)data;
+ (*active)++;
+}
+
+static void handle_disable(struct libseat *backend, void *data) {
+ (void)backend;
+ int *active = (int *)data;
+ (*active)--;
+
+ libseat_disable_seat(backend);
+}
+
+int main(int argc, char *argv[]) {
+ (void)argc;
+ (void)argv;
+
+ int active = 0;
+ struct libseat_seat_listener listener = {
+ .enable_seat = handle_enable,
+ .disable_seat = handle_disable,
+ };
+ struct libseat *backend = libseat_open_seat(&listener, &active);
+ fprintf(stderr, "libseat_open_seat(listener: %p, userdata: %p) = %p\n", (void *)&listener,
+ (void *)&active, (void *)backend);
+ if (backend == NULL) {
+ fprintf(stderr, "libseat_open_seat() failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ while (active == 0) {
+ fprintf(stderr, "waiting for activation...\n");
+ libseat_dispatch(backend, -1);
+ }
+ fprintf(stderr, "active!\n");
+
+ int fd, device;
+ device = libseat_open_device(backend, "/dev/dri/card0", &fd);
+ fprintf(stderr, "libseat_open_device(backend: %p, path: %s, fd: %p) = %d\n",
+ (void *)backend, "/dev/dri/card0", (void *)&fd, device);
+ if (device == -1) {
+ fprintf(stderr, "libseat_open_device() failed: %s\n", strerror(errno));
+ libseat_close_seat(backend);
+ return 1;
+ }
+
+ close(fd);
+ libseat_close_device(backend, device);
+ libseat_close_seat(backend);
+ return 0;
+}
diff --git a/include/backend.h b/include/backend.h
new file mode 100644
index 0000000..e310a49
--- /dev/null
+++ b/include/backend.h
@@ -0,0 +1,32 @@
+#ifndef _SEATD_BACKEND_H
+#define _SEATD_BACKEND_H
+
+#include "libseat.h"
+
+struct libseat_impl;
+struct libseat_seat_listener;
+
+struct libseat {
+ const struct libseat_impl *impl;
+};
+
+struct named_backend {
+ const char *name;
+ const struct libseat_impl *backend;
+};
+
+struct libseat_impl {
+ struct libseat *(*open_seat)(struct libseat_seat_listener *listener, void *data);
+ int (*disable_seat)(struct libseat *seat);
+ int (*close_seat)(struct libseat *seat);
+ const char *(*seat_name)(struct libseat *seat);
+
+ int (*open_device)(struct libseat *seat, const char *path, int *fd);
+ int (*close_device)(struct libseat *seat, int device_id);
+ int (*switch_session)(struct libseat *seat, int session);
+
+ int (*get_fd)(struct libseat *seat);
+ int (*dispatch)(struct libseat *seat, int timeout);
+};
+
+#endif
diff --git a/include/client.h b/include/client.h
new file mode 100644
index 0000000..6084980
--- /dev/null
+++ b/include/client.h
@@ -0,0 +1,37 @@
+#ifndef _SEATD_CLIENT_H
+#define _SEATD_CLIENT_H
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "connection.h"
+#include "list.h"
+
+struct server;
+
+struct client {
+ struct server *server;
+ struct event_source_fd *event_source;
+ struct connection connection;
+
+ pid_t pid;
+ uid_t uid;
+ gid_t gid;
+
+ struct seat *seat;
+ int seat_vt;
+
+ struct list devices;
+};
+
+struct client *client_create(struct server *server, int client_fd);
+void client_kill(struct client *client);
+void client_destroy(struct client *client);
+
+int client_handle_connection(int fd, uint32_t mask, void *data);
+int client_get_session(struct client *client);
+int client_enable_seat(struct client *client);
+int client_disable_seat(struct client *client);
+
+#endif
diff --git a/include/compiler.h b/include/compiler.h
new file mode 100644
index 0000000..d0f6ed2
--- /dev/null
+++ b/include/compiler.h
@@ -0,0 +1,18 @@
+#ifndef _VISIBILITY_H
+#define _VISIBILITY_H
+
+#ifdef __GNUC__
+#define ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end)))
+#else
+#define ATTRIB_PRINTF(start, end)
+#endif
+
+#if defined(__GNUC__) && __GNUC__ >= 4
+#define LIBSEAT_EXPORT __attribute__((visibility("default")))
+#define ALWAYS_INLINE __attribute__((always_inline)) inline
+#else
+#define LIBSEAT_EXPORT
+#define ALWAYS_INLINE inline
+#endif
+
+#endif
diff --git a/include/connection.h b/include/connection.h
new file mode 100644
index 0000000..6c57901
--- /dev/null
+++ b/include/connection.h
@@ -0,0 +1,36 @@
+#ifndef _SEATD_CONNECTION_H
+#define _SEATD_CONNECTION_H
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#define CONNECTION_BUFFER_SIZE 1024
+
+#define MAX_FDS_OUT 8
+
+struct connection_buffer {
+ uint32_t head, tail;
+ char data[CONNECTION_BUFFER_SIZE];
+};
+
+struct connection {
+ struct connection_buffer in, out;
+ struct connection_buffer fds_in, fds_out;
+ int fd;
+ bool want_flush;
+};
+
+int connection_read(struct connection *connection);
+int connection_flush(struct connection *connection);
+
+int connection_put(struct connection *connection, const void *data, size_t count);
+int connection_put_fd(struct connection *connection, int fd);
+
+size_t connection_pending(struct connection *connection);
+int connection_get(struct connection *connection, void *dst, size_t count);
+int connection_get_fd(struct connection *connection);
+void connection_restore(struct connection *connection, size_t count);
+
+void connection_close_fds(struct connection *connection);
+
+#endif
diff --git a/include/drm.h b/include/drm.h
new file mode 100644
index 0000000..1012c89
--- /dev/null
+++ b/include/drm.h
@@ -0,0 +1,10 @@
+#ifndef _SEATD_DRM_H
+#define _SEATD_DRM_H
+
+#include <sys/types.h>
+
+int drm_set_master(int fd);
+int drm_drop_master(int fd);
+int dev_is_drm(dev_t device);
+
+#endif
diff --git a/include/evdev.h b/include/evdev.h
new file mode 100644
index 0000000..0f20e3e
--- /dev/null
+++ b/include/evdev.h
@@ -0,0 +1,9 @@
+#ifndef _SEATD_EVDEV_H
+#define _SEATD_EVDEV_H
+
+#include <sys/types.h>
+
+int evdev_revoke(int fd);
+int dev_is_evdev(dev_t device);
+
+#endif
diff --git a/include/libseat.h b/include/libseat.h
new file mode 100644
index 0000000..7e9ef1f
--- /dev/null
+++ b/include/libseat.h
@@ -0,0 +1,140 @@
+#ifndef _LIBSEAT_H
+#define _LIBSEAT_H
+
+#include <stdarg.h>
+
+/*
+ * An opaque struct containing an opened seat, created by libseat_open-seat and
+ * destroyed by libseat_close_seat.
+ */
+struct libseat;
+
+/*
+ * A seat event listener, given to libseat_open_seat.
+ */
+struct libseat_seat_listener {
+ /*
+ * The seat has been enabled, and is now valid for use. Re-open all seat
+ * devices to ensure that they are operational, as existing fds may have
+ * had their functionality blocked or revoked.
+ *
+ * Must be non-NULL.
+ */
+ void (*enable_seat)(struct libseat *seat, void *userdata);
+
+ /*
+ * The seat has been disabled. This event signals that the application
+ * is going to lose its seat access. The event *must* be acknowledged
+ * with libseat_disable_seat shortly after receiving this event.
+ *
+ * If the recepient fails to acknowledge the event in time, seat devices
+ * may be forcibly revoked by the seat provider.
+ *
+ * Must be non-NULL.
+ */
+ void (*disable_seat)(struct libseat *seat, void *userdata);
+};
+
+/*
+ * Opens a seat, taking control of it if possible and returning a pointer to
+ * the libseat instance. If LIBSEAT_BACKEND is set, the specified backend is
+ * used. Otherwise, the first successful backend will be used.
+ *
+ * The seat listener specified is used to signal events on the seat, and must
+ * be non-NULL. The userdata pointer will be provided in all calls to the seat
+ * listener.
+ *
+ * The available backends, if enabled at compile-time, are: seatd, logind and
+ * builtin.
+ *
+ * To use builtin, the process must have CAP_SYS_ADMIN or be root at the time
+ * of the call. These privileges can be dropped at any point after the call.
+ *
+ * The returned pointer must be destroyed with libseat_close_seat.
+ *
+ * Returns a pointer to an opaque libseat struct on success. Returns NULL and
+ * sets errno on error.
+ */
+struct libseat *libseat_open_seat(struct libseat_seat_listener *listener, void *userdata);
+
+/*
+ * Disables a seat, used in response to a disable_seat event. After disabling
+ * the seat, the seat devices must not be used until enable_seat is received,
+ * and all requests on the seat will fail during this period.
+ *
+ * Returns 0 on success. -1 and sets errno on error.
+ */
+int libseat_disable_seat(struct libseat *seat);
+
+/*
+ * Closes the seat. This frees the libseat structure.
+ *
+ * Returns 0 on success. Returns -1 and sets errno on error.
+ */
+int libseat_close_seat(struct libseat *seat);
+
+/*
+ * Opens a device on the seat, returning its device ID and placing the fd in
+ * the specified pointer.
+ *
+ * This will only succeed if the seat is active and the device is of a type
+ * permitted for opening on the backend, such as drm and evdev.
+ *
+ * The device may be revoked in some situations, such as in situations where a
+ * seat session switch is being forced.
+ *
+ * Returns the device id on success. Returns -1 and sets errno on error.
+ */
+int libseat_open_device(struct libseat *seat, const char *path, int *fd);
+
+/*
+ * Closes a device that has been opened on the seat using the device_id from
+ * libseat_open_device.
+ *
+ * Returns 0 on success. Returns -1 and sets errno on error.
+ */
+int libseat_close_device(struct libseat *seat, int device_id);
+
+/*
+ * Retrieves the name of the seat that is currently made available through the
+ * provided libseat instance.
+ *
+ * The returned string is owned by the libseat instance, and must not be
+ * modified. It remains valid as long as the seat is open.
+ */
+const char *libseat_seat_name(struct libseat *seat);
+
+/*
+ * Requests that the seat switches session to the specified session number.
+ * For seats that are VT-bound, the session number matches the VT number, and
+ * switching session results in a VT switch.
+ *
+ * A call to libseat_switch_session does not imply that a switch will occur,
+ * and the caller should assume that the session continues unaffected.
+ *
+ * Returns 0 on success. Returns -1 and sets errno on error.
+ */
+int libseat_switch_session(struct libseat *seat, int session);
+
+/*
+ * Retrieve the pollable connection fd for a given libseat instance. Used to
+ * poll the libseat connection for events that need to be dispatched.
+ *
+ * Returns a pollable fd on success. Returns -1 and sets errno on error.
+ */
+int libseat_get_fd(struct libseat *seat);
+
+/*
+ * Reads and dispatches events on the libseat connection fd.
+ *
+ * The specified timeout dictates how long libseat might wait for data if none
+ * is available: 0 means that no wait will occur, -1 means that libseat might
+ * wait indefinitely for data to arrive, while > 0 is the maximum wait in
+ * milliseconds that might occur.
+ *
+ * Returns a positive number signifying processed internal messages on success.
+ * Returns 0-if no messages were processed. Returns -1 and sets errno on error.
+ */
+int libseat_dispatch(struct libseat *seat, int timeout);
+
+#endif
diff --git a/include/list.h b/include/list.h
new file mode 100644
index 0000000..3f2ac6c
--- /dev/null
+++ b/include/list.h
@@ -0,0 +1,22 @@
+#ifndef _SEATD_LIST_H
+#define _SEATD_LIST_H
+
+#include <stddef.h>
+
+struct list {
+ size_t capacity;
+ size_t length;
+ void **items;
+};
+
+void list_init(struct list *);
+void list_free(struct list *list);
+void list_add(struct list *list, void *item);
+void list_insert(struct list *list, size_t index, void *item);
+void list_del(struct list *list, size_t index);
+void list_concat(struct list *list, struct list *source);
+void list_truncate(struct list *list);
+void *list_pop_front(struct list *list);
+size_t list_find(struct list *list, const void *item);
+
+#endif
diff --git a/include/log.h b/include/log.h
new file mode 100644
index 0000000..18a573e
--- /dev/null
+++ b/include/log.h
@@ -0,0 +1,51 @@
+#ifndef _LOG_H
+#define _LOG_H
+
+#include "compiler.h"
+#include <stdarg.h>
+
+enum libseat_log_level {
+ LIBSEAT_SILENT = 0,
+ LIBSEAT_ERROR = 1,
+ LIBSEAT_INFO = 2,
+ LIBSEAT_DEBUG = 3,
+ LIBSEAT_LOG_LEVEL_LAST,
+};
+
+void libseat_log_init(enum libseat_log_level level);
+
+void _libseat_logf(enum libseat_log_level level, const char *fmt, ...) ATTRIB_PRINTF(2, 3);
+
+#ifdef LIBSEAT_REL_SRC_DIR
+#define _LIBSEAT_FILENAME ((const char *)__FILE__ + sizeof(LIBSEAT_REL_SRC_DIR) - 1)
+#else
+#define _LIBSEAT_FILENAME __FILE__
+#endif
+
+#define log_infof(fmt, ...) \
+ _libseat_logf(LIBSEAT_INFO, "[%s:%d] %s: " fmt, _LIBSEAT_FILENAME, __LINE__, __func__, \
+ ##__VA_ARGS__)
+
+#define log_info(str) \
+ _libseat_logf(LIBSEAT_INFO, "[%s:%d] %s: %s", _LIBSEAT_FILENAME, __LINE__, __func__, str)
+
+#define log_errorf(fmt, ...) \
+ _libseat_logf(LIBSEAT_ERROR, "[%s:%d] %s: " fmt, _LIBSEAT_FILENAME, __LINE__, __func__, \
+ ##__VA_ARGS__)
+
+#define log_error(str) \
+ _libseat_logf(LIBSEAT_ERROR, "[%s:%d] %s: %s", _LIBSEAT_FILENAME, __LINE__, __func__, str)
+
+#ifdef DEBUG
+#define log_debugf(fmt, ...) \
+ _libseat_logf(LIBSEAT_DEBUG, "[%s:%d] %s: " fmt, _LIBSEAT_FILENAME, __LINE__, __func__, \
+ ##__VA_ARGS__)
+
+#define log_debug(str) \
+ _libseat_logf(LIBSEAT_DEBUG, "[%s:%d] %s: %s", _LIBSEAT_FILENAME, __LINE__, __func__, str)
+#else
+#define log_debugf(fmt, ...)
+#define log_debug(str)
+#endif
+
+#endif
diff --git a/include/poller.h b/include/poller.h
new file mode 100644
index 0000000..f867df9
--- /dev/null
+++ b/include/poller.h
@@ -0,0 +1,137 @@
+#ifndef _SEATD_POLLER_H
+#define _SEATD_POLLER_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+struct poller;
+struct event_source_fd;
+struct event_source_signal;
+
+/*
+ * These are the event types available from the poller.
+ */
+#define EVENT_READABLE 0x1
+#define EVENT_WRITABLE 0x4
+#define EVENT_ERROR 0x8
+#define EVENT_HANGUP 0x10
+
+/**
+ * The callback type used by event_source_fd, passed to poller_add_fd.
+ */
+typedef int (*event_source_fd_func_t)(int fd, uint32_t mask, void *data);
+
+/**
+ * The interface that an event_source_fd must implement.
+ */
+struct event_source_fd_impl {
+ int (*update)(struct event_source_fd *event_source, uint32_t mask);
+ int (*destroy)(struct event_source_fd *event_source);
+};
+
+/**
+ * The fd poller base class. This must be created by poller_add_fd.
+ */
+struct event_source_fd {
+ const struct event_source_fd_impl *impl;
+ event_source_fd_func_t func;
+
+ int fd;
+ uint32_t mask;
+ void *data;
+};
+
+/**
+ * Removes the event_source_fd from the poller and frees the structure.
+ */
+int event_source_fd_destroy(struct event_source_fd *event_source);
+
+/**
+ * Updates the poll mask applied to this fd, effective on the next poll.
+ */
+int event_source_fd_update(struct event_source_fd *event_source, uint32_t mask);
+
+/**
+ * The callback type used by event_source_signal, passed to poller_add_signal.
+ */
+typedef int (*event_source_signal_func_t)(int signal, void *data);
+
+/**
+ * The interface that an event_source_signal must implement.
+ */
+struct event_source_signal_impl {
+ int (*destroy)(struct event_source_signal *event_source);
+};
+
+/*
+ * The signal poller base class. This must be created by poller_add_signal.
+ */
+struct event_source_signal {
+ const struct event_source_signal_impl *impl;
+ event_source_signal_func_t func;
+
+ int signal;
+ void *data;
+};
+
+/**
+ * Removes the event_source_siganl from the poller and frees the structure.
+ */
+int event_source_signal_destroy(struct event_source_signal *event_source);
+
+/**
+ * The interface that a poll backend must implement.
+ */
+struct poll_impl {
+ struct poller *(*create)(void);
+ int (*destroy)(struct poller *);
+
+ struct event_source_fd *(*add_fd)(struct poller *, int fd, uint32_t mask,
+ event_source_fd_func_t func, void *data);
+ struct event_source_signal *(*add_signal)(struct poller *, int signal,
+ event_source_signal_func_t func, void *data);
+
+ int (*poll)(struct poller *);
+};
+
+/**
+ * The poller base class. This must be created by poller_create.
+ */
+struct poller {
+ const struct poll_impl *impl;
+};
+
+/**
+ * Creates a poller with the best available polling backend. This poller must
+ * be torn down with poller_destroy when it is no longer needed.
+ */
+struct poller *poller_create(void);
+
+/**
+ * Destroys the poller. This destroys all remaining event sources, tears down
+ * the poller and frees the structure.
+ */
+int poller_destroy(struct poller *poller);
+
+/**
+ * Create an fd event source with the provided initial parameters. This event
+ * source must be torn down with event_source_fd_destroy when it is no longer
+ * needed.
+ */
+struct event_source_fd *poller_add_fd(struct poller *poller, int fd, uint32_t mask,
+ event_source_fd_func_t func, void *data);
+
+/**
+ * Create signal event source with the provided initial parameters. This event
+ * source must be torn down with event_source_signal_destroy when it is no
+ * longer needed.
+ */
+struct event_source_signal *poller_add_signal(struct poller *poller, int signal,
+ event_source_signal_func_t func, void *data);
+
+/**
+ * Poll the poller. I don't know what you were expecting.
+ */
+int poller_poll(struct poller *poller);
+
+#endif
diff --git a/include/protocol.h b/include/protocol.h
new file mode 100644
index 0000000..7444b85
--- /dev/null
+++ b/include/protocol.h
@@ -0,0 +1,64 @@
+#ifndef _SEATD_CONSTANTS_H
+#define _SEATD_CONSTANTS_H
+
+#define MAX_PATH_LEN 256
+#define MAX_SEAT_LEN 64
+#define MAX_SEAT_DEVICES 128
+#define MAX_SESSION_LEN 64
+
+#define CLIENT_EVENT(opcode) (opcode)
+#define SERVER_EVENT(opcode) ((opcode) + (1 << 15))
+
+#define CLIENT_OPEN_SEAT CLIENT_EVENT(1)
+#define CLIENT_CLOSE_SEAT CLIENT_EVENT(2)
+#define CLIENT_OPEN_DEVICE CLIENT_EVENT(3)
+#define CLIENT_CLOSE_DEVICE CLIENT_EVENT(4)
+#define CLIENT_DISABLE_SEAT CLIENT_EVENT(5)
+#define CLIENT_SWITCH_SESSION CLIENT_EVENT(6)
+
+#define SERVER_SEAT_OPENED SERVER_EVENT(1)
+#define SERVER_SEAT_CLOSED SERVER_EVENT(2)
+#define SERVER_DEVICE_OPENED SERVER_EVENT(3)
+#define SERVER_DEVICE_CLOSED SERVER_EVENT(4)
+#define SERVER_DISABLE_SEAT SERVER_EVENT(5)
+#define SERVER_ENABLE_SEAT SERVER_EVENT(6)
+#define SERVER_ERROR SERVER_EVENT(0x7FFF)
+
+#include <stdint.h>
+
+struct proto_header {
+ uint16_t opcode;
+ uint16_t size;
+};
+
+struct proto_client_open_device {
+ uint16_t path_len;
+ // NULL-terminated byte-sequence path_len long follows
+};
+
+struct proto_client_close_device {
+ int device_id;
+};
+
+struct proto_client_switch_session {
+ int session;
+};
+
+struct proto_server_seat_opened {
+ uint16_t seat_name_len;
+ // NULL-terminated byte-sequence seat_name_len long follows
+};
+
+struct proto_server_device_opened {
+ int device_id;
+};
+
+struct proto_server_device_closed {
+ int device_id;
+};
+
+struct proto_server_error {
+ int error_code;
+};
+
+#endif
diff --git a/include/seat.h b/include/seat.h
new file mode 100644
index 0000000..e52a961
--- /dev/null
+++ b/include/seat.h
@@ -0,0 +1,47 @@
+#ifndef _SEATD_SEAT_H
+#define _SEATD_SEAT_H
+
+#include "list.h"
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+struct client;
+
+struct seat_device {
+ int device_id;
+ int fd;
+ int ref_cnt;
+ bool active;
+ char *path;
+ dev_t dev;
+};
+
+struct seat {
+ char *seat_name;
+ struct list clients;
+ struct client *active_client;
+ struct client *next_client;
+
+ bool vt_bound;
+ bool vt_pending_ack;
+ int next_vt;
+};
+
+struct seat *seat_create(const char *name, bool vt_bound);
+void seat_destroy(struct seat *seat);
+
+int seat_add_client(struct seat *seat, struct client *client);
+int seat_remove_client(struct seat *seat, struct client *client);
+int seat_open_client(struct seat *seat, struct client *client);
+int seat_close_client(struct seat *seat, struct client *client);
+
+struct seat_device *seat_open_device(struct client *client, const char *path);
+int seat_close_device(struct client *client, struct seat_device *seat_device);
+struct seat_device *seat_find_device(struct client *client, int device_id);
+
+int seat_set_next_session(struct seat *seat, int session);
+int seat_activate(struct seat *seat);
+int seat_prepare_vt_switch(struct seat *seat);
+
+#endif
diff --git a/include/server.h b/include/server.h
new file mode 100644
index 0000000..11de2c5
--- /dev/null
+++ b/include/server.h
@@ -0,0 +1,26 @@
+#ifndef _SEATD_SERVER_H
+#define _SEATD_SERVER_H
+
+#include <stdbool.h>
+
+#include "list.h"
+
+struct poller;
+struct client;
+
+struct server {
+ bool running;
+ struct poller *poller;
+
+ struct list seats;
+};
+
+struct server *server_create(void);
+void server_destroy(struct server *server);
+
+struct seat *server_get_seat(struct server *server, const char *seat_name);
+
+int server_listen(struct server *server, const char *path);
+int server_add_client(struct server *server, int fd);
+
+#endif
diff --git a/include/terminal.h b/include/terminal.h
new file mode 100644
index 0000000..c2a49ff
--- /dev/null
+++ b/include/terminal.h
@@ -0,0 +1,14 @@
+#ifndef _SEATD_TERMINAL_H
+#define _SEATD_TERMINAL_H
+
+#include <stdbool.h>
+
+int terminal_setup(int vt);
+int terminal_teardown(int vt);
+int terminal_current_vt(void);
+int terminal_switch_vt(int vt);
+int terminal_ack_switch(void);
+int terminal_set_keyboard(int vt, bool enable);
+int terminal_set_graphics(int vt, bool enable);
+
+#endif
diff --git a/libseat/backend/logind.c b/libseat/backend/logind.c
new file mode 100644
index 0000000..ec19bba
--- /dev/null
+++ b/libseat/backend/logind.c
@@ -0,0 +1,782 @@
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#if defined(HAVE_ELOGIND)
+#include <elogind/sd-bus.h>
+#include <elogind/sd-login.h>
+#elif defined(HAVE_SYSTEMD)
+#include <systemd/sd-bus.h>
+#include <systemd/sd-login.h>
+#else
+#error logind backend requires either elogind or systemd
+#endif
+
+#include "backend.h"
+#include "drm.h"
+#include "libseat.h"
+#include "list.h"
+
+struct backend_logind {
+ struct libseat base;
+ struct libseat_seat_listener *seat_listener;
+ void *seat_listener_data;
+
+ sd_bus *bus;
+ char *id;
+ char *seat;
+ char *path;
+ char *seat_path;
+
+ bool can_graphical;
+ bool active;
+ bool initial_setup;
+ int has_drm;
+};
+
+const struct libseat_impl logind_impl;
+static struct backend_logind *backend_logind_from_libseat_backend(struct libseat *base);
+
+static void destroy(struct backend_logind *backend) {
+ assert(backend);
+ if (backend->bus != NULL) {
+ sd_bus_unref(backend->bus);
+ }
+ free(backend->id);
+ free(backend->seat);
+ free(backend->path);
+ free(backend->seat_path);
+ free(backend);
+}
+
+static int close_seat(struct libseat *base) {
+ struct backend_logind *backend = backend_logind_from_libseat_backend(base);
+ destroy(backend);
+ return 0;
+}
+
+static int open_device(struct libseat *base, const char *path, int *fd) {
+ struct backend_logind *session = backend_logind_from_libseat_backend(base);
+
+ int ret;
+ int tmpfd = -1;
+ sd_bus_message *msg = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ struct stat st;
+ if (stat(path, &st) < 0) {
+ return -1;
+ }
+
+ ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", session->path,
+ "org.freedesktop.login1.Session", "TakeDevice", &error, &msg, "uu",
+ major(st.st_rdev), minor(st.st_rdev));
+ if (ret < 0) {
+ tmpfd = -1;
+ goto out;
+ }
+
+ int paused = 0;
+ ret = sd_bus_message_read(msg, "hb", &tmpfd, &paused);
+ if (ret < 0) {
+ tmpfd = -1;
+ goto out;
+ }
+
+ // The original fd seems to be closed when the message is freed
+ // so we just clone it.
+ tmpfd = fcntl(tmpfd, F_DUPFD_CLOEXEC, 0);
+ if (tmpfd < 0) {
+ tmpfd = -1;
+ goto out;
+ }
+
+ if (dev_is_drm(st.st_rdev)) {
+ session->has_drm++;
+ }
+
+ *fd = tmpfd;
+out:
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(msg);
+ return tmpfd;
+}
+
+static int close_device(struct libseat *base, int device_id) {
+ struct backend_logind *session = backend_logind_from_libseat_backend(base);
+ if (device_id < 0) {
+ return -1;
+ }
+
+ int fd = device_id;
+
+ struct stat st = {0};
+ if (fstat(fd, &st) < 0) {
+ close(fd);
+ return -1;
+ }
+ if (dev_is_drm(st.st_rdev)) {
+ session->has_drm--;
+ assert(session->has_drm >= 0);
+ }
+ close(fd);
+
+ sd_bus_message *msg = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus_call_method(session->bus, "org.freedesktop.login1", session->path,
+ "org.freedesktop.login1.Session", "ReleaseDevice", &error, &msg, "uu",
+ major(st.st_rdev), minor(st.st_rdev));
+
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(msg);
+
+ return -1;
+}
+
+static int switch_session(struct libseat *base, int s) {
+ struct backend_logind *session = backend_logind_from_libseat_backend(base);
+ if (s >= UINT16_MAX || s < 0) {
+ return -1;
+ }
+
+ // Only seat0 has VTs associated with it
+ if (strcmp(session->seat, "seat0") != 0) {
+ return true;
+ }
+
+ int ret;
+ sd_bus_message *msg = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ ret = sd_bus_call_method(session->bus, "org.freedesktop.login1",
+ "/org/freedesktop/login1/seat/seat0", "org.freedesktop.login1.Seat",
+ "SwitchTo", &error, &msg, "u", (uint32_t)s);
+
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(msg);
+ return ret >= 0;
+}
+
+static int disable_seat(struct libseat *base) {
+ (void)base;
+ return 0;
+}
+
+static int get_fd(struct libseat *base) {
+ struct backend_logind *backend = backend_logind_from_libseat_backend(base);
+ return sd_bus_get_fd(backend->bus);
+}
+
+static int poll_connection(struct backend_logind *backend, int timeout) {
+ struct pollfd fd = {
+ .fd = sd_bus_get_fd(backend->bus),
+ .events = POLLIN,
+ };
+
+ if (poll(&fd, 1, timeout) == -1) {
+ if (errno == EAGAIN || errno == EINTR) {
+ return 0;
+ } else {
+ return -1;
+ }
+ }
+
+ if (fd.revents & (POLLERR | POLLHUP)) {
+ return -1;
+ }
+ return 0;
+}
+
+static int dispatch_background(struct libseat *base, int timeout) {
+ struct backend_logind *backend = backend_logind_from_libseat_backend(base);
+ if (backend->initial_setup) {
+ backend->initial_setup = false;
+ if (backend->active) {
+ backend->seat_listener->enable_seat(&backend->base,
+ backend->seat_listener_data);
+ } else {
+ backend->seat_listener->disable_seat(&backend->base,
+ backend->seat_listener_data);
+ }
+ }
+
+ int total_dispatched = 0;
+ int dispatched = 0;
+ while ((dispatched = sd_bus_process(backend->bus, NULL)) > 0) {
+ total_dispatched += dispatched;
+ }
+ if (total_dispatched == 0 && timeout != 0) {
+ if (poll_connection(backend, timeout) == -1) {
+ return -1;
+ }
+ while ((dispatched = sd_bus_process(backend->bus, NULL)) > 0) {
+ total_dispatched += dispatched;
+ }
+ }
+ return total_dispatched;
+}
+
+static const char *seat_name(struct libseat *base) {
+ struct backend_logind *backend = backend_logind_from_libseat_backend(base);
+
+ if (backend->seat == NULL) {
+ return NULL;
+ }
+ return backend->seat;
+}
+
+static struct backend_logind *backend_logind_from_libseat_backend(struct libseat *base) {
+ assert(base->impl == &logind_impl);
+ return (struct backend_logind *)base;
+}
+
+static bool session_activate(struct backend_logind *session) {
+ sd_bus_message *msg = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ int ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", session->path,
+ "org.freedesktop.login1.Session", "Activate", &error, &msg, "");
+
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(msg);
+ return ret >= 0;
+}
+
+static bool take_control(struct backend_logind *session) {
+ sd_bus_message *msg = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ int ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", session->path,
+ "org.freedesktop.login1.Session", "TakeControl", &error, &msg,
+ "b", false);
+
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(msg);
+ return ret >= 0;
+}
+
+static void set_active(struct backend_logind *backend, bool active) {
+ if (backend->active == active) {
+ return;
+ }
+
+ backend->active = active;
+ if (active) {
+ backend->seat_listener->enable_seat(&backend->base, backend->seat_listener_data);
+ } else {
+ backend->seat_listener->disable_seat(&backend->base, backend->seat_listener_data);
+ }
+}
+
+static int pause_device(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) {
+ struct backend_logind *session = userdata;
+
+ uint32_t major, minor;
+ const char *type;
+ int ret = sd_bus_message_read(msg, "uus", &major, &minor, &type);
+ if (ret < 0) {
+ goto error;
+ }
+
+ if (dev_is_drm(makedev(major, minor)) && strcmp(type, "gone") != 0) {
+ assert(session->has_drm > 0);
+ set_active(session, false);
+ }
+
+ if (strcmp(type, "pause") == 0) {
+ sd_bus_call_method(session->bus, "org.freedesktop.login1", session->path,
+ "org.freedesktop.login1.Session", "PauseDeviceComplete",
+ ret_error, &msg, "uu", major, minor);
+ }
+
+error:
+ return 0;
+}
+
+static int resume_device(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) {
+ (void)ret_error;
+ struct backend_logind *session = userdata;
+ int ret;
+
+ int fd;
+ uint32_t major, minor;
+ ret = sd_bus_message_read(msg, "uuh", &major, &minor, &fd);
+ if (ret < 0) {
+ goto error;
+ }
+
+ if (dev_is_drm(makedev(major, minor))) {
+ assert(session->has_drm > 0);
+ set_active(session, true);
+ }
+
+error:
+ return 0;
+}
+
+static int session_properties_changed(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) {
+ (void)ret_error;
+ struct backend_logind *session = userdata;
+ int ret = 0;
+
+ if (session->has_drm > 0) {
+ return 0;
+ }
+
+ // PropertiesChanged arg 1: interface
+ const char *interface;
+ ret = sd_bus_message_read_basic(msg, 's', &interface); // skip path
+ if (ret < 0) {
+ goto error;
+ }
+
+ if (strcmp(interface, "org.freedesktop.login1.Session") != 0) {
+ // not interesting for us; ignore
+ return 0;
+ }
+
+ // PropertiesChanged arg 2: changed properties with values
+ ret = sd_bus_message_enter_container(msg, 'a', "{sv}");
+ if (ret < 0) {
+ goto error;
+ }
+
+ const char *s;
+ while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) {
+ ret = sd_bus_message_read_basic(msg, 's', &s);
+ if (ret < 0) {
+ goto error;
+ }
+
+ if (strcmp(s, "Active") == 0) {
+ int ret;
+ ret = sd_bus_message_enter_container(msg, 'v', "b");
+ if (ret < 0) {
+ goto error;
+ }
+
+ bool active;
+ ret = sd_bus_message_read_basic(msg, 'b', &active);
+ if (ret < 0) {
+ goto error;
+ }
+
+ set_active(session, active);
+ return 0;
+ } else {
+ sd_bus_message_skip(msg, "{sv}");
+ }
+
+ ret = sd_bus_message_exit_container(msg);
+ if (ret < 0) {
+ goto error;
+ }
+ }
+
+ if (ret < 0) {
+ goto error;
+ }
+
+ ret = sd_bus_message_exit_container(msg);
+ if (ret < 0) {
+ goto error;
+ }
+
+ // PropertiesChanged arg 3: changed properties without values
+ sd_bus_message_enter_container(msg, 'a', "s");
+ while ((ret = sd_bus_message_read_basic(msg, 's', &s)) > 0) {
+ if (strcmp(s, "Active") == 0) {
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+ bool active;
+ ret = sd_bus_get_property_trivial(session->bus, "org.freedesktop.login1",
+ session->path,
+ "org.freedesktop.login1.Session",
+ "Active", &error, 'b', &active);
+ if (ret < 0) {
+ return 0;
+ }
+
+ set_active(session, active);
+ return 0;
+ }
+ }
+
+ if (ret < 0) {
+ goto error;
+ }
+
+ return 0;
+
+error:
+ return 0;
+}
+
+static int seat_properties_changed(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) {
+ (void)ret_error;
+ struct backend_logind *session = userdata;
+ int ret = 0;
+
+ if (session->has_drm > 0) {
+ return 0;
+ }
+
+ // PropertiesChanged arg 1: interface
+ const char *interface;
+ ret = sd_bus_message_read_basic(msg, 's', &interface); // skip path
+ if (ret < 0) {
+ goto error;
+ }
+
+ if (strcmp(interface, "org.freedesktop.login1.Seat") != 0) {
+ // not interesting for us; ignore
+ return 0;
+ }
+
+ // PropertiesChanged arg 2: changed properties with values
+ ret = sd_bus_message_enter_container(msg, 'a', "{sv}");
+ if (ret < 0) {
+ goto error;
+ }
+
+ const char *s;
+ while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) {
+ ret = sd_bus_message_read_basic(msg, 's', &s);
+ if (ret < 0) {
+ goto error;
+ }
+
+ if (strcmp(s, "CanGraphical") == 0) {
+ int ret;
+ ret = sd_bus_message_enter_container(msg, 'v', "b");
+ if (ret < 0) {
+ goto error;
+ }
+
+ ret = sd_bus_message_read_basic(msg, 'b', &session->can_graphical);
+ if (ret < 0) {
+ goto error;
+ }
+
+ return 0;
+ } else {
+ sd_bus_message_skip(msg, "{sv}");
+ }
+
+ ret = sd_bus_message_exit_container(msg);
+ if (ret < 0) {
+ goto error;
+ }
+ }
+
+ if (ret < 0) {
+ goto error;
+ }
+
+ ret = sd_bus_message_exit_container(msg);
+ if (ret < 0) {
+ goto error;
+ }
+
+ // PropertiesChanged arg 3: changed properties without values
+ sd_bus_message_enter_container(msg, 'a', "s");
+ while ((ret = sd_bus_message_read_basic(msg, 's', &s)) > 0) {
+ if (strcmp(s, "CanGraphical") == 0) {
+ session->can_graphical = sd_seat_can_graphical(session->seat);
+ return 0;
+ }
+ }
+
+ if (ret < 0) {
+ goto error;
+ }
+
+ return 0;
+
+error:
+ return 0;
+}
+
+static bool add_signal_matches(struct backend_logind *backend) {
+ static const char *logind = "org.freedesktop.login1";
+ static const char *session_interface = "org.freedesktop.login1.Session";
+ static const char *property_interface = "org.freedesktop.DBus.Properties";
+ int ret;
+
+ ret = sd_bus_match_signal(backend->bus, NULL, logind, backend->path, session_interface,
+ "PauseDevice", pause_device, backend);
+ if (ret < 0) {
+ return false;
+ }
+
+ ret = sd_bus_match_signal(backend->bus, NULL, logind, backend->path, session_interface,
+ "ResumeDevice", resume_device, backend);
+ if (ret < 0) {
+ return false;
+ }
+
+ ret = sd_bus_match_signal(backend->bus, NULL, logind, backend->path, property_interface,
+ "PropertiesChanged", session_properties_changed, backend);
+ if (ret < 0) {
+ return false;
+ }
+
+ ret = sd_bus_match_signal(backend->bus, NULL, logind, backend->seat_path, property_interface,
+ "PropertiesChanged", seat_properties_changed, backend);
+ if (ret < 0) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool contains_str(const char *needle, const char **haystack) {
+ for (int i = 0; haystack[i]; i++) {
+ if (strcmp(haystack[i], needle) == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool get_greeter_session(char **session_id) {
+ char *class = NULL;
+ char **user_sessions = NULL;
+ int user_session_count = sd_uid_get_sessions(getuid(), 1, &user_sessions);
+
+ if (user_session_count < 0) {
+ goto out;
+ }
+
+ if (user_session_count == 0) {
+ goto out;
+ }
+
+ for (int i = 0; i < user_session_count; ++i) {
+ int ret = sd_session_get_class(user_sessions[i], &class);
+ if (ret < 0) {
+ continue;
+ }
+
+ if (strcmp(class, "greeter") == 0) {
+ *session_id = strdup(user_sessions[i]);
+ goto out;
+ }
+ }
+
+out:
+ free(class);
+ for (int i = 0; i < user_session_count; ++i) {
+ free(user_sessions[i]);
+ }
+ free(user_sessions);
+
+ return *session_id != NULL;
+}
+
+static bool find_session_path(struct backend_logind *session) {
+ int ret;
+ sd_bus_message *msg = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager", "GetSession", &error, &msg, "s",
+ session->id);
+ if (ret < 0) {
+ goto out;
+ }
+
+ const char *path;
+ ret = sd_bus_message_read(msg, "o", &path);
+ if (ret < 0) {
+ goto out;
+ }
+ session->path = strdup(path);
+
+out:
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(msg);
+
+ return ret >= 0;
+}
+
+static bool find_seat_path(struct backend_logind *session) {
+ int ret;
+ sd_bus_message *msg = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager", "GetSeat", &error, &msg, "s",
+ session->seat);
+ if (ret < 0) {
+ goto out;
+ }
+
+ const char *path;
+ ret = sd_bus_message_read(msg, "o", &path);
+ if (ret < 0) {
+ goto out;
+ }
+ session->seat_path = strdup(path);
+
+out:
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(msg);
+
+ return ret >= 0;
+}
+
+static bool get_display_session(char **session_id) {
+ assert(session_id != NULL);
+ char *xdg_session_id = getenv("XDG_SESSION_ID");
+ char *type = NULL;
+ char *state = NULL;
+
+ if (xdg_session_id) {
+ // This just checks whether the supplied session ID is valid
+ if (sd_session_is_active(xdg_session_id) < 0) {
+ goto error;
+ }
+ *session_id = strdup(xdg_session_id);
+ return true;
+ }
+
+ // If there's a session active for the current process then just use
+ // that
+ int ret = sd_pid_get_session(getpid(), session_id);
+ if (ret == 0) {
+ return true;
+ }
+
+ // Find any active sessions for the user if the process isn't part of an
+ // active session itself
+ ret = sd_uid_get_display(getuid(), session_id);
+ if (ret < 0 && ret != -ENODATA) {
+ goto error;
+ }
+
+ if (ret != 0 && !get_greeter_session(session_id)) {
+ goto error;
+ }
+
+ assert(*session_id != NULL);
+
+ // Check that the available session is graphical
+ ret = sd_session_get_type(*session_id, &type);
+ if (ret < 0) {
+ goto error;
+ }
+
+ const char *graphical_session_types[] = {"wayland", "x11", "mir", NULL};
+ if (!contains_str(type, graphical_session_types)) {
+ goto error;
+ }
+
+ // Check that the session is active
+ ret = sd_session_get_state(*session_id, &state);
+ if (ret < 0) {
+ goto error;
+ }
+
+ const char *active_states[] = {"active", "online", NULL};
+ if (!contains_str(state, active_states)) {
+ goto error;
+ }
+
+ free(type);
+ free(state);
+ return true;
+
+error:
+ free(type);
+ free(state);
+ free(*session_id);
+ *session_id = NULL;
+
+ return false;
+}
+
+static struct libseat *logind_open_seat(struct libseat_seat_listener *listener, void *data) {
+ struct backend_logind *backend = calloc(1, sizeof(struct backend_logind));
+ if (backend == NULL) {
+ return NULL;
+ }
+
+ if (!get_display_session(&backend->id)) {
+ goto error;
+ }
+
+ int ret = sd_session_get_seat(backend->id, &backend->seat);
+ if (ret < 0) {
+ goto error;
+ }
+
+ ret = sd_bus_default_system(&backend->bus);
+ if (ret < 0) {
+ goto error;
+ }
+
+ if (!find_session_path(backend)) {
+ goto error;
+ }
+
+ if (!find_seat_path(backend)) {
+ goto error;
+ }
+
+ if (!add_signal_matches(backend)) {
+ goto error;
+ }
+
+ if (!session_activate(backend)) {
+ goto error;
+ }
+
+ if (!take_control(backend)) {
+ goto error;
+ }
+
+ backend->can_graphical = sd_seat_can_graphical(backend->seat);
+ while (!backend->can_graphical) {
+ if (poll_connection(backend, -1) == -1) {
+ goto error;
+ }
+ }
+
+ backend->initial_setup = true;
+ backend->active = true;
+ backend->seat_listener = listener;
+ backend->seat_listener_data = data;
+ backend->base.impl = &logind_impl;
+
+ return &backend->base;
+
+error:
+ if (backend != NULL) {
+ destroy(backend);
+ }
+ return NULL;
+}
+
+const struct libseat_impl logind_impl = {
+ .open_seat = logind_open_seat,
+ .disable_seat = disable_seat,
+ .close_seat = close_seat,
+ .seat_name = seat_name,
+ .open_device = open_device,
+ .close_device = close_device,
+ .switch_session = switch_session,
+ .get_fd = get_fd,
+ .dispatch = dispatch_background,
+};
diff --git a/libseat/backend/seatd.c b/libseat/backend/seatd.c
new file mode 100644
index 0000000..3b4a04b
--- /dev/null
+++ b/libseat/backend/seatd.c
@@ -0,0 +1,545 @@
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "backend.h"
+#include "connection.h"
+#include "libseat.h"
+#include "list.h"
+#include "log.h"
+#include "protocol.h"
+
+#ifdef BUILTIN_ENABLED
+#include "poller.h"
+#include "server.h"
+#endif
+
+const struct libseat_impl seatd_impl;
+const struct libseat_impl builtin_impl;
+
+struct pending_event {
+ int opcode;
+};
+
+struct backend_seatd {
+ struct libseat base;
+ struct connection connection;
+ struct libseat_seat_listener *seat_listener;
+ void *seat_listener_data;
+ struct list pending_events;
+
+ char seat_name[MAX_SEAT_LEN];
+};
+
+static int set_nonblock(int fd) {
+ int flags;
+ if ((flags = fcntl(fd, F_GETFD)) == -1 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
+ return -1;
+ }
+ if ((flags = fcntl(fd, F_GETFL)) == -1 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+ return -1;
+ }
+ return 0;
+}
+
+static int seatd_connect(void) {
+ union {
+ struct sockaddr_un unix;
+ struct sockaddr generic;
+ } addr = {0};
+ int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1) {
+ return -1;
+ }
+ if (set_nonblock(fd) == -1) {
+ close(fd);
+ return -1;
+ }
+ char *path = getenv("SEATD_SOCK");
+ if (path == NULL) {
+ path = "/run/seatd.sock";
+ }
+ addr.unix.sun_family = AF_UNIX;
+ strncpy(addr.unix.sun_path, path, sizeof addr.unix.sun_path);
+ socklen_t size = offsetof(struct sockaddr_un, sun_path) + strlen(addr.unix.sun_path);
+ if (connect(fd, &addr.generic, size) == -1) {
+ close(fd);
+ return -1;
+ };
+ return fd;
+}
+
+static struct backend_seatd *backend_seatd_from_libseat_backend(struct libseat *base) {
+ assert(base);
+#ifdef BUILTIN_ENABLED
+ assert(base->impl == &seatd_impl || base->impl == &builtin_impl);
+#else
+ assert(base->impl == &seatd_impl);
+#endif
+ return (struct backend_seatd *)base;
+}
+
+static void handle_enable_seat(struct backend_seatd *backend) {
+ if (backend->seat_listener != NULL && backend->seat_listener->enable_seat != NULL) {
+ backend->seat_listener->enable_seat(&backend->base, backend->seat_listener_data);
+ }
+}
+
+static void handle_disable_seat(struct backend_seatd *backend) {
+ if (backend->seat_listener != NULL && backend->seat_listener->disable_seat != NULL) {
+ backend->seat_listener->disable_seat(&backend->base, backend->seat_listener_data);
+ }
+}
+
+static size_t read_header(struct connection *connection, uint16_t expected_opcode) {
+ struct proto_header header;
+ if (connection_get(connection, &header, sizeof header) == -1) {
+ return SIZE_MAX;
+ }
+ if (header.opcode != expected_opcode) {
+ connection_restore(connection, sizeof header);
+ errno = EBADMSG;
+ return SIZE_MAX;
+ }
+
+ return header.size;
+}
+
+static int queue_event(struct backend_seatd *backend, int opcode) {
+ struct pending_event *ev = calloc(1, sizeof(struct pending_event));
+ if (ev == NULL) {
+ return -1;
+ }
+
+ ev->opcode = opcode;
+ list_add(&backend->pending_events, ev);
+ return 0;
+}
+
+static void execute_events(struct backend_seatd *backend) {
+ while (backend->pending_events.length > 0) {
+ struct pending_event *ev = list_pop_front(&backend->pending_events);
+ int opcode = ev->opcode;
+ free(ev);
+
+ switch (opcode) {
+ case SERVER_DISABLE_SEAT:
+ handle_disable_seat(backend);
+ break;
+ case SERVER_ENABLE_SEAT:
+ handle_enable_seat(backend);
+ break;
+ default:
+ abort();
+ }
+ }
+}
+
+static int dispatch_pending(struct backend_seatd *backend, int *opcode) {
+ int packets = 0;
+ struct proto_header header;
+ while (connection_get(&backend->connection, &header, sizeof header) != -1) {
+ packets++;
+ switch (header.opcode) {
+ case SERVER_DISABLE_SEAT:
+ case SERVER_ENABLE_SEAT:
+ queue_event(backend, header.opcode);
+ break;
+ default:
+ if (opcode != NULL &&
+ connection_pending(&backend->connection) >= header.size) {
+ *opcode = header.opcode;
+ }
+ connection_restore(&backend->connection, sizeof header);
+ return packets;
+ }
+ }
+ return packets;
+}
+
+static int poll_connection(struct backend_seatd *backend, int timeout) {
+ struct pollfd fd = {
+ .fd = backend->connection.fd,
+ .events = POLLIN,
+ };
+
+ if (poll(&fd, 1, timeout) == -1) {
+ return (errno == EAGAIN || errno == EINTR) ? 0 : -1;
+ }
+
+ if (fd.revents & (POLLERR | POLLHUP)) {
+ errno = EPIPE;
+ return -1;
+ }
+
+ int len = 0;
+ if (fd.revents & POLLIN) {
+ len = connection_read(&backend->connection);
+ if (len == 0 || (len == -1 && errno != EAGAIN)) {
+ return -1;
+ }
+ }
+
+ return len;
+}
+
+static int dispatch(struct backend_seatd *backend) {
+ if (connection_flush(&backend->connection) == -1) {
+ return -1;
+ }
+ int opcode = 0;
+ while (dispatch_pending(backend, &opcode) == 0 && opcode == 0) {
+ if (poll_connection(backend, -1) == -1) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void check_error(struct connection *connection) {
+ struct proto_header header;
+ if (connection_get(connection, &header, sizeof header) == -1) {
+ return;
+ }
+ if (header.opcode != SERVER_ERROR) {
+ errno = EBADMSG;
+ return;
+ }
+
+ struct proto_server_error msg;
+ if (connection_get(connection, &msg, sizeof msg) == -1) {
+ return;
+ }
+
+ errno = msg.error_code;
+}
+
+static int get_fd(struct libseat *base) {
+ struct backend_seatd *backend = backend_seatd_from_libseat_backend(base);
+ return backend->connection.fd;
+}
+
+static int dispatch_background(struct libseat *base, int timeout) {
+ struct backend_seatd *backend = backend_seatd_from_libseat_backend(base);
+ int dispatched = dispatch_pending(backend, NULL);
+ if (dispatched > 0) {
+ // We don't want to block if we dispatched something, as the
+ // caller might be waiting for the result. However, we'd also
+ // like to read anything pending.
+ timeout = 0;
+ }
+ int read = 0;
+ if (timeout == 0) {
+ read = connection_read(&backend->connection);
+ } else {
+ read = poll_connection(backend, timeout);
+ }
+ if (read > 0) {
+ dispatched += dispatch_pending(backend, NULL);
+ } else if (read == -1 && errno != EAGAIN) {
+ return -1;
+ }
+
+ execute_events(backend);
+ return dispatched;
+}
+
+static void destroy(struct backend_seatd *backend) {
+ if (backend->connection.fd != -1) {
+ close(backend->connection.fd);
+ backend->connection.fd = -1;
+ }
+ connection_close_fds(&backend->connection);
+ for (size_t idx = 0; idx < backend->pending_events.length; idx++) {
+ free(backend->pending_events.items[idx]);
+ }
+ list_free(&backend->pending_events);
+ free(backend);
+}
+
+static struct libseat *_open_seat(struct libseat_seat_listener *listener, void *data, int fd) {
+ struct backend_seatd *backend = calloc(1, sizeof(struct backend_seatd));
+ if (backend == NULL) {
+ close(fd);
+ return NULL;
+ }
+ backend->seat_listener = listener;
+ backend->seat_listener_data = data;
+ backend->connection.fd = fd;
+ backend->base.impl = &seatd_impl;
+ list_init(&backend->pending_events);
+
+ struct proto_header header = {
+ .opcode = CLIENT_OPEN_SEAT,
+ .size = 0,
+ };
+
+ if (connection_put(&backend->connection, &header, sizeof header) == -1 ||
+ dispatch(backend) == -1) {
+ destroy(backend);
+ return NULL;
+ }
+
+ size_t size = read_header(&backend->connection, SERVER_SEAT_OPENED);
+ if (size == SIZE_MAX) {
+ check_error(&backend->connection);
+ destroy(backend);
+ return NULL;
+ }
+
+ struct proto_server_seat_opened rmsg;
+ if (sizeof rmsg > size) {
+ errno = EBADMSG;
+ return NULL;
+ }
+
+ if (connection_get(&backend->connection, &rmsg, sizeof rmsg) == -1) {
+ return NULL;
+ };
+
+ if (sizeof rmsg + rmsg.seat_name_len > size ||
+ rmsg.seat_name_len >= sizeof backend->seat_name) {
+ errno = EBADMSG;
+ return NULL;
+ }
+
+ if (connection_get(&backend->connection, backend->seat_name, rmsg.seat_name_len) == -1) {
+ return NULL;
+ };
+
+ return &backend->base;
+}
+
+static struct libseat *open_seat(struct libseat_seat_listener *listener, void *data) {
+ int fd = seatd_connect();
+ if (fd == -1) {
+ return NULL;
+ }
+
+ return _open_seat(listener, data, fd);
+}
+
+static int close_seat(struct libseat *base) {
+ struct backend_seatd *backend = backend_seatd_from_libseat_backend(base);
+
+ struct proto_header header = {
+ .opcode = CLIENT_CLOSE_SEAT,
+ .size = 0,
+ };
+
+ if (connection_put(&backend->connection, &header, sizeof header) == -1 ||
+ dispatch(backend) == -1) {
+ destroy(backend);
+ return -1;
+ }
+
+ size_t size = read_header(&backend->connection, SERVER_SEAT_CLOSED);
+ if (size == SIZE_MAX) {
+ check_error(&backend->connection);
+ destroy(backend);
+ return -1;
+ }
+
+ destroy(backend);
+ return 0;
+}
+
+static const char *seat_name(struct libseat *base) {
+ struct backend_seatd *backend = backend_seatd_from_libseat_backend(base);
+ return backend->seat_name;
+}
+
+static int open_device(struct libseat *base, const char *path, int *fd) {
+ struct backend_seatd *backend = backend_seatd_from_libseat_backend(base);
+
+ size_t pathlen = strlen(path) + 1;
+ if (pathlen > MAX_PATH_LEN) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ struct proto_client_open_device msg = {
+ .path_len = (uint16_t)pathlen,
+ };
+ struct proto_header header = {
+ .opcode = CLIENT_OPEN_DEVICE,
+ .size = sizeof msg + pathlen,
+ };
+
+ if (connection_put(&backend->connection, &header, sizeof header) == -1 ||
+ connection_put(&backend->connection, &msg, sizeof msg) == -1 ||
+ connection_put(&backend->connection, path, pathlen) == -1 || dispatch(backend) == -1) {
+ return -1;
+ }
+
+ size_t size = read_header(&backend->connection, SERVER_DEVICE_OPENED);
+ if (size == SIZE_MAX) {
+ check_error(&backend->connection);
+ return -1;
+ }
+
+ struct proto_server_device_opened rmsg;
+ if (sizeof rmsg > size) {
+ errno = EBADMSG;
+ return -1;
+ }
+ if (connection_get(&backend->connection, &rmsg, sizeof rmsg) == -1) {
+ return -1;
+ }
+
+ int received_fd = connection_get_fd(&backend->connection);
+ if (received_fd == -1) {
+ return -1;
+ }
+
+ *fd = received_fd;
+ return rmsg.device_id;
+}
+
+static int close_device(struct libseat *base, int device_id) {
+ struct backend_seatd *backend = backend_seatd_from_libseat_backend(base);
+ if (device_id < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ struct proto_client_close_device msg = {
+ .device_id = device_id,
+ };
+ struct proto_header header = {
+ .opcode = CLIENT_CLOSE_DEVICE,
+ .size = sizeof msg,
+ };
+
+ if (connection_put(&backend->connection, &header, sizeof header) == -1 ||
+ connection_put(&backend->connection, &msg, sizeof msg) == -1 || dispatch(backend) == -1) {
+ return -1;
+ }
+
+ size_t size = read_header(&backend->connection, SERVER_DEVICE_CLOSED);
+ if (size == SIZE_MAX) {
+ check_error(&backend->connection);
+ return -1;
+ }
+
+ struct proto_server_device_closed rmsg;
+ if (sizeof rmsg > size) {
+ errno = EBADMSG;
+ return -1;
+ }
+ if (connection_get(&backend->connection, &rmsg, sizeof rmsg) == -1) {
+ return -1;
+ }
+ if (rmsg.device_id != device_id) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int switch_session(struct libseat *base, int session) {
+ struct backend_seatd *backend = backend_seatd_from_libseat_backend(base);
+ if (session < 0) {
+ return -1;
+ }
+
+ struct proto_client_switch_session msg = {
+ .session = session,
+ };
+ struct proto_header header = {
+ .opcode = CLIENT_SWITCH_SESSION,
+ .size = sizeof msg,
+ };
+
+ if (connection_put(&backend->connection, &header, sizeof header) == -1 ||
+ connection_put(&backend->connection, &msg, sizeof msg) == -1 ||
+ connection_flush(&backend->connection) == -1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int disable_seat(struct libseat *base) {
+ struct backend_seatd *backend = backend_seatd_from_libseat_backend(base);
+ struct proto_header header = {
+ .opcode = CLIENT_DISABLE_SEAT,
+ .size = 0,
+ };
+
+ if (connection_put(&backend->connection, &header, sizeof header) == -1 ||
+ connection_flush(&backend->connection) == -1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+const struct libseat_impl seatd_impl = {
+ .open_seat = open_seat,
+ .disable_seat = disable_seat,
+ .close_seat = close_seat,
+ .seat_name = seat_name,
+ .open_device = open_device,
+ .close_device = close_device,
+ .switch_session = switch_session,
+ .get_fd = get_fd,
+ .dispatch = dispatch_background,
+};
+
+#ifdef BUILTIN_ENABLED
+static struct libseat *builtin_open_seat(struct libseat_seat_listener *listener, void *data) {
+ int fds[2];
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1) {
+ return NULL;
+ }
+
+ pid_t pid = fork();
+ if (pid == -1) {
+ close(fds[0]);
+ close(fds[1]);
+ return NULL;
+ } else if (pid == 0) {
+ int fd = fds[0];
+ struct server *server = server_create();
+ if (server == NULL) {
+ close(fd);
+ exit(1);
+ }
+ if (server_add_client(server, fd) == -1) {
+ exit(1);
+ }
+ while (server->running) {
+ if (poller_poll(server->poller) == -1) {
+ exit(1);
+ }
+ }
+ close(fd);
+ exit(0);
+ } else {
+ int fd = fds[1];
+ return _open_seat(listener, data, fd);
+ }
+}
+
+const struct libseat_impl builtin_impl = {
+ .open_seat = builtin_open_seat,
+ .disable_seat = disable_seat,
+ .close_seat = close_seat,
+ .seat_name = seat_name,
+ .open_device = open_device,
+ .close_device = close_device,
+ .switch_session = switch_session,
+ .get_fd = get_fd,
+ .dispatch = dispatch_background,
+};
+#endif
diff --git a/libseat/libseat.c b/libseat/libseat.c
new file mode 100644
index 0000000..a1aed18
--- /dev/null
+++ b/libseat/libseat.c
@@ -0,0 +1,111 @@
+#include <assert.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "backend.h"
+#include "compiler.h"
+#include "libseat.h"
+#include "log.h"
+
+extern const struct libseat_impl seatd_impl;
+extern const struct libseat_impl logind_impl;
+extern const struct libseat_impl builtin_impl;
+
+static const struct named_backend impls[] = {
+#ifdef SEATD_ENABLED
+ {"seatd", &seatd_impl},
+#endif
+#ifdef LOGIND_ENABLED
+ {"logind", &logind_impl},
+#endif
+#ifdef BUILTIN_ENABLED
+ {"builtin", &builtin_impl},
+#endif
+ {NULL, NULL},
+};
+
+#if !defined(SEATD_ENABLED) && !defined(LOGIND_ENABLED) && !defined(BUILTIN_ENABLED)
+#error At least one backend must be enabled
+#endif
+
+LIBSEAT_EXPORT struct libseat *libseat_open_seat(struct libseat_seat_listener *listener, void *data) {
+ if (listener == NULL) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ char *loglevel = getenv("SEATD_LOGLEVEL");
+ enum libseat_log_level level = LIBSEAT_SILENT;
+ 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);
+
+ char *backend_type = getenv("LIBSEAT_BACKEND");
+ struct libseat *backend = NULL;
+ for (const struct named_backend *iter = impls; iter->backend != NULL; iter++) {
+ log_debugf("libseat_open_seat: trying backend '%s'", iter->name);
+ if (backend_type != NULL && strcmp(backend_type, iter->name) != 0) {
+ continue;
+ }
+ backend = iter->backend->open_seat(listener, data);
+ if (backend != NULL) {
+ log_infof("libseat_open_seat: seat opened with backend '%s'", iter->name);
+ break;
+ }
+ }
+ if (backend == NULL) {
+ errno = ENOSYS;
+ }
+ return backend;
+}
+
+LIBSEAT_EXPORT int libseat_disable_seat(struct libseat *seat) {
+ assert(seat && seat->impl);
+ return seat->impl->disable_seat(seat);
+}
+
+LIBSEAT_EXPORT int libseat_close_seat(struct libseat *seat) {
+ assert(seat && seat->impl);
+ return seat->impl->close_seat(seat);
+}
+
+LIBSEAT_EXPORT const char *libseat_seat_name(struct libseat *seat) {
+ assert(seat && seat->impl);
+ return seat->impl->seat_name(seat);
+}
+
+LIBSEAT_EXPORT int libseat_open_device(struct libseat *seat, const char *path, int *fd) {
+ assert(seat && seat->impl);
+ return seat->impl->open_device(seat, path, fd);
+}
+
+LIBSEAT_EXPORT int libseat_close_device(struct libseat *seat, int device_id) {
+ assert(seat && seat->impl);
+ return seat->impl->close_device(seat, device_id);
+}
+
+LIBSEAT_EXPORT int libseat_get_fd(struct libseat *seat) {
+ assert(seat && seat->impl);
+ return seat->impl->get_fd(seat);
+}
+
+LIBSEAT_EXPORT int libseat_dispatch(struct libseat *seat, int timeout) {
+ assert(seat && seat->impl);
+ return seat->impl->dispatch(seat, timeout);
+}
+
+LIBSEAT_EXPORT int libseat_switch_session(struct libseat *seat, int session) {
+ assert(seat && seat->impl);
+ return seat->impl->switch_session(seat, session);
+}
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..9f7c9a8
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,157 @@
+project(
+ 'seatd',
+ 'c',
+ version: '0.1',
+ meson_version: '>=0.47.0',
+ default_options: [
+ 'c_std=c11',
+ 'warning_level=3',
+ 'werror=true',
+ ],
+)
+
+add_project_arguments(
+ [
+ '-Wundef',
+ '-Wunused',
+ '-Wlogical-op',
+ '-Wmissing-include-dirs',
+ '-Wold-style-definition', # nop
+ '-Wpointer-arith',
+ '-Wstrict-prototypes',
+ '-Wimplicit-fallthrough',
+ '-Wmissing-prototypes',
+ '-Wno-unknown-warning',
+ '-Wvla',
+ '-D_XOPEN_SOURCE=9000',
+ '-Wl,--exclude-libs=ALL',
+ '-fvisibility=hidden',
+ ],
+ language: 'c',
+)
+
+# Hacks
+source_root = meson.current_source_dir().split('/')
+build_root = meson.build_root().split('/')
+relative_dir_parts = []
+i = 0
+in_prefix = true
+foreach p : build_root
+ if i >= source_root.length() or not in_prefix or p != source_root[i]
+ in_prefix = false
+ relative_dir_parts += '..'
+ endif
+ i += 1
+endforeach
+i = 0
+in_prefix = true
+foreach p : source_root
+ if i >= build_root.length() or not in_prefix or build_root[i] != p
+ in_prefix = false
+ relative_dir_parts += p
+ endif
+ i += 1
+endforeach
+
+if get_option('buildtype').startswith('debug')
+ add_project_arguments('-DDEBUG', language : 'c')
+endif
+
+
+add_project_arguments(
+ '-DLIBSEAT_REL_SRC_DIR="@0@"'.format(join_paths(relative_dir_parts) + '/'),
+ language: 'c',
+)
+
+private_files = [
+ 'common/connection.c',
+ 'common/list.c',
+ 'common/log.c',
+]
+
+private_deps = []
+
+server_files = [
+ 'common/log.c',
+ 'common/list.c',
+ 'common/terminal.c',
+ 'common/connection.c',
+ 'common/evdev.c',
+ 'common/drm.c',
+ 'seatd/poll/basic_poller.c',
+ 'seatd/poll/poller.c',
+ 'seatd/seat.c',
+ 'seatd/client.c',
+ 'seatd/server.c',
+]
+
+if get_option('seatd').enabled()
+ private_files += 'libseat/backend/seatd.c'
+ add_project_arguments('-DSEATD_ENABLED=1', language: 'c')
+endif
+
+if get_option('logind').enabled()
+ logind = dependency('libsystemd', required: false)
+ add_project_arguments('-DLOGIND_ENABLED=1', language: 'c')
+ if logind.found()
+ add_project_arguments('-DHAVE_SYSTEMD=1', language: 'c')
+ else
+ logind = dependency('libelogind')
+ add_project_arguments('-DHAVE_ELOGIND=1', language: 'c')
+ endif
+
+ private_files += [
+ 'libseat/backend/logind.c',
+ 'common/drm.c',
+ ]
+ private_deps += logind
+endif
+
+if get_option('builtin').enabled()
+ add_project_arguments('-DBUILTIN_ENABLED=1', language: 'c')
+ private_files += server_files
+endif
+
+private_lib = static_library(
+ 'seat-private',
+ private_files,
+ dependencies: private_deps,
+ include_directories: [include_directories('.', 'include')],
+)
+
+lib = library(
+ 'seat', # This results in the library being called 'libseat'
+ [ 'libseat/libseat.c' ],
+ link_with: private_lib,
+ include_directories: [include_directories('.', 'include')],
+ install: true,
+)
+
+install_headers('include/libseat.h')
+
+pkgconfig = import('pkgconfig')
+pkgconfig.generate(lib,
+ version: meson.project_version(),
+ filebase: 'libseat',
+ name: 'libseat',
+ description: 'Seat management library',
+)
+
+if get_option('server').enabled()
+ executable(
+ 'seatd',
+ [ server_files, 'seatd/seatd.c' ],
+ include_directories: [include_directories('.', 'include')],
+ install: true,
+ )
+endif
+
+if get_option('examples').enabled()
+ executable(
+ 'simpletest',
+ ['examples/simpletest/main.c'],
+ link_with: [lib],
+ include_directories: [include_directories('.', 'include')],
+ install: false,
+ )
+endif
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644
index 0000000..fc05dc2
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1,5 @@
+option('logind', type: 'feature', value: 'disabled', description: 'logind support')
+option('seatd', type: 'feature', value: 'enabled', description: 'seatd support')
+option('builtin', type: 'feature', value: 'disabled', description: 'builtin seatd server')
+option('server', type: 'feature', value: 'enabled', description: 'seatd server')
+option('examples', type: 'feature', value: 'enabled', description: 'libseat example programs')
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;
+}