Initial implementation of seatd and libseat

This commit is contained in:
Kenny Levinsen 2020-07-31 00:22:18 +02:00
parent f85434de66
commit 61716a2c77
32 changed files with 4744 additions and 0 deletions

312
common/connection.c Normal file
View file

@ -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);
}

23
common/drm.c Normal file
View file

@ -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;
}

15
common/evdev.c Normal file
View file

@ -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;
}

76
common/list.c Normal file
View file

@ -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;
}

80
common/log.c Normal file
View file

@ -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;
}

202
common/terminal.c Normal file
View file

@ -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;
}