diff options
Diffstat (limited to 'common')
| -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 | 
6 files changed, 708 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; +} | 
