diff options
| author | Kenny Levinsen <kl@kl.wtf> | 2020-07-31 00:22:18 +0200 | 
|---|---|---|
| committer | Kenny Levinsen <kl@kl.wtf> | 2020-07-31 00:22:18 +0200 | 
| commit | 61716a2c77dfde9addf6b41a6d72d26a8584150e (patch) | |
| tree | 537cd84661955497bdb304f88896e36896df4e5f | |
| parent | f85434de666f10da0cbcaccdbb7d88917c5fa887 (diff) | |
| download | seatd-61716a2c77dfde9addf6b41a6d72d26a8584150e.tar.xz | |
Initial implementation of seatd and libseat
| -rw-r--r-- | common/connection.c | 312 | ||||
| -rw-r--r-- | common/drm.c | 23 | ||||
| -rw-r--r-- | common/evdev.c | 15 | ||||
| -rw-r--r-- | common/list.c | 76 | ||||
| -rw-r--r-- | common/log.c | 80 | ||||
| -rw-r--r-- | common/terminal.c | 202 | ||||
| -rw-r--r-- | examples/simpletest/main.c | 60 | ||||
| -rw-r--r-- | include/backend.h | 32 | ||||
| -rw-r--r-- | include/client.h | 37 | ||||
| -rw-r--r-- | include/compiler.h | 18 | ||||
| -rw-r--r-- | include/connection.h | 36 | ||||
| -rw-r--r-- | include/drm.h | 10 | ||||
| -rw-r--r-- | include/evdev.h | 9 | ||||
| -rw-r--r-- | include/libseat.h | 140 | ||||
| -rw-r--r-- | include/list.h | 22 | ||||
| -rw-r--r-- | include/log.h | 51 | ||||
| -rw-r--r-- | include/poller.h | 137 | ||||
| -rw-r--r-- | include/protocol.h | 64 | ||||
| -rw-r--r-- | include/seat.h | 47 | ||||
| -rw-r--r-- | include/server.h | 26 | ||||
| -rw-r--r-- | include/terminal.h | 14 | ||||
| -rw-r--r-- | libseat/backend/logind.c | 782 | ||||
| -rw-r--r-- | libseat/backend/seatd.c | 545 | ||||
| -rw-r--r-- | libseat/libseat.c | 111 | ||||
| -rw-r--r-- | meson.build | 157 | ||||
| -rw-r--r-- | meson_options.txt | 5 | ||||
| -rw-r--r-- | seatd/client.c | 484 | ||||
| -rw-r--r-- | seatd/poll/basic_poller.c | 386 | ||||
| -rw-r--r-- | seatd/poll/poller.c | 53 | ||||
| -rw-r--r-- | seatd/seat.c | 521 | ||||
| -rw-r--r-- | seatd/seatd.c | 57 | ||||
| -rw-r--r-- | seatd/server.c | 232 | 
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; +} | 
