Initial implementation of seatd and libseat
This commit is contained in:
parent
f85434de66
commit
61716a2c77
32 changed files with 4744 additions and 0 deletions
312
common/connection.c
Normal file
312
common/connection.c
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "compiler.h"
|
||||||
|
#include "connection.h"
|
||||||
|
|
||||||
|
#define CLEN (CMSG_LEN(MAX_FDS_OUT * sizeof(int)))
|
||||||
|
|
||||||
|
ALWAYS_INLINE static uint32_t connection_buffer_mask(const uint32_t idx) {
|
||||||
|
return idx & (CONNECTION_BUFFER_SIZE - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE static uint32_t connection_buffer_size(const struct connection_buffer *b) {
|
||||||
|
return b->head - b->tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE static void connection_buffer_consume(struct connection_buffer *b, const size_t size) {
|
||||||
|
b->tail += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE static void connection_buffer_restore(struct connection_buffer *b, const size_t size) {
|
||||||
|
b->tail -= size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* connection_buffer_get_iov prepares I/O vectors pointing to our ring buffer.
|
||||||
|
* Two may be used if the buffer has wrapped around.
|
||||||
|
*/
|
||||||
|
static void connection_buffer_get_iov(struct connection_buffer *b, struct iovec *iov, int *count) {
|
||||||
|
uint32_t head = connection_buffer_mask(b->head);
|
||||||
|
uint32_t tail = connection_buffer_mask(b->tail);
|
||||||
|
if (tail < head) {
|
||||||
|
iov[0].iov_base = b->data + tail;
|
||||||
|
iov[0].iov_len = head - tail;
|
||||||
|
*count = 1;
|
||||||
|
} else if (head == 0) {
|
||||||
|
iov[0].iov_base = b->data + tail;
|
||||||
|
iov[0].iov_len = sizeof b->data - tail;
|
||||||
|
*count = 1;
|
||||||
|
} else {
|
||||||
|
iov[0].iov_base = b->data + tail;
|
||||||
|
iov[0].iov_len = sizeof b->data - tail;
|
||||||
|
iov[1].iov_base = b->data;
|
||||||
|
iov[1].iov_len = head;
|
||||||
|
*count = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* connection_buffer_put_iov prepares I/O vectors pointing to our ring buffer.
|
||||||
|
* Two may be used if the buffer has wrapped around.
|
||||||
|
*/
|
||||||
|
static void connection_buffer_put_iov(struct connection_buffer *b, struct iovec *iov, int *count) {
|
||||||
|
uint32_t head = connection_buffer_mask(b->head);
|
||||||
|
uint32_t tail = connection_buffer_mask(b->tail);
|
||||||
|
if (head < tail) {
|
||||||
|
iov[0].iov_base = b->data + head;
|
||||||
|
iov[0].iov_len = tail - head;
|
||||||
|
*count = 1;
|
||||||
|
} else if (tail == 0) {
|
||||||
|
iov[0].iov_base = b->data + head;
|
||||||
|
iov[0].iov_len = sizeof b->data - head;
|
||||||
|
*count = 1;
|
||||||
|
} else {
|
||||||
|
iov[0].iov_base = b->data + head;
|
||||||
|
iov[0].iov_len = sizeof b->data - head;
|
||||||
|
iov[1].iov_base = b->data;
|
||||||
|
iov[1].iov_len = tail;
|
||||||
|
*count = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* connection_buffer_copy copies from our ring buffer into a linear buffer.
|
||||||
|
*/
|
||||||
|
static void connection_buffer_copy(const struct connection_buffer *b, void *data, const size_t count) {
|
||||||
|
uint32_t tail = connection_buffer_mask(b->tail);
|
||||||
|
if (tail + count <= sizeof b->data) {
|
||||||
|
memcpy(data, b->data + tail, count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t size = sizeof b->data - tail;
|
||||||
|
memcpy(data, b->data + tail, size);
|
||||||
|
memcpy((char *)data + size, b->data, count - size);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* connection_buffer_copy copies from a linear buffer into our ring buffer.
|
||||||
|
*/
|
||||||
|
static int connection_buffer_put(struct connection_buffer *b, const void *data, const size_t count) {
|
||||||
|
if (count > sizeof(b->data)) {
|
||||||
|
errno = EOVERFLOW;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t head = connection_buffer_mask(b->head);
|
||||||
|
if (head + count <= sizeof b->data) {
|
||||||
|
memcpy(b->data + head, data, count);
|
||||||
|
} else {
|
||||||
|
uint32_t size = sizeof b->data - head;
|
||||||
|
memcpy(b->data + head, data, size);
|
||||||
|
memcpy(b->data, (const char *)data + size, count - size);
|
||||||
|
}
|
||||||
|
|
||||||
|
b->head += count;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* close_fds closes all fds within a connection_buffer
|
||||||
|
*/
|
||||||
|
static void connection_buffer_close_fds(struct connection_buffer *buffer) {
|
||||||
|
size_t size = connection_buffer_size(buffer);
|
||||||
|
if (size == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int fds[sizeof(buffer->data) / sizeof(int)];
|
||||||
|
connection_buffer_copy(buffer, fds, size);
|
||||||
|
int count = size / sizeof fds[0];
|
||||||
|
size = count * sizeof fds[0];
|
||||||
|
for (int idx = 0; idx < count; idx++) {
|
||||||
|
close(fds[idx]);
|
||||||
|
}
|
||||||
|
connection_buffer_consume(buffer, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* build_cmsg prepares a cmsg from a buffer full of fds
|
||||||
|
*/
|
||||||
|
static void build_cmsg(struct connection_buffer *buffer, char *data, int *clen) {
|
||||||
|
size_t size = connection_buffer_size(buffer);
|
||||||
|
if (size > MAX_FDS_OUT * sizeof(int)) {
|
||||||
|
size = MAX_FDS_OUT * sizeof(int);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size <= 0) {
|
||||||
|
*clen = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct cmsghdr *cmsg = (struct cmsghdr *)data;
|
||||||
|
cmsg->cmsg_level = SOL_SOCKET;
|
||||||
|
cmsg->cmsg_type = SCM_RIGHTS;
|
||||||
|
cmsg->cmsg_len = CMSG_LEN(size);
|
||||||
|
connection_buffer_copy(buffer, CMSG_DATA(cmsg), size);
|
||||||
|
*clen = cmsg->cmsg_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int decode_cmsg(struct connection_buffer *buffer, struct msghdr *msg) {
|
||||||
|
bool overflow = false;
|
||||||
|
struct cmsghdr *cmsg;
|
||||||
|
for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) {
|
||||||
|
if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size = cmsg->cmsg_len - CMSG_LEN(0);
|
||||||
|
size_t max = sizeof(buffer->data) - connection_buffer_size(buffer);
|
||||||
|
if (size > max || overflow) {
|
||||||
|
overflow = true;
|
||||||
|
size /= sizeof(int);
|
||||||
|
for (size_t idx = 0; idx < size; idx++) {
|
||||||
|
close(((int *)CMSG_DATA(cmsg))[idx]);
|
||||||
|
}
|
||||||
|
} else if (connection_buffer_put(buffer, CMSG_DATA(cmsg), size) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overflow) {
|
||||||
|
errno = EOVERFLOW;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int connection_read(struct connection *connection) {
|
||||||
|
if (connection_buffer_size(&connection->in) >= sizeof(connection->in.data)) {
|
||||||
|
errno = EOVERFLOW;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count;
|
||||||
|
struct iovec iov[2];
|
||||||
|
connection_buffer_put_iov(&connection->in, iov, &count);
|
||||||
|
|
||||||
|
char cmsg[CLEN];
|
||||||
|
struct msghdr msg = {
|
||||||
|
.msg_name = NULL,
|
||||||
|
.msg_namelen = 0,
|
||||||
|
.msg_iov = iov,
|
||||||
|
.msg_iovlen = count,
|
||||||
|
.msg_control = cmsg,
|
||||||
|
.msg_controllen = sizeof cmsg,
|
||||||
|
.msg_flags = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
int len;
|
||||||
|
do {
|
||||||
|
len = recvmsg(connection->fd, &msg, MSG_DONTWAIT | MSG_CMSG_CLOEXEC);
|
||||||
|
if (len == -1 && errno != EINTR)
|
||||||
|
return -1;
|
||||||
|
} while (len == -1);
|
||||||
|
|
||||||
|
if (decode_cmsg(&connection->fds_in, &msg) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
connection->in.head += len;
|
||||||
|
|
||||||
|
return connection_buffer_size(&connection->in);
|
||||||
|
}
|
||||||
|
|
||||||
|
int connection_flush(struct connection *connection) {
|
||||||
|
if (!connection->want_flush) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t tail = connection->out.tail;
|
||||||
|
while (connection->out.head - connection->out.tail > 0) {
|
||||||
|
int count;
|
||||||
|
struct iovec iov[2];
|
||||||
|
connection_buffer_get_iov(&connection->out, iov, &count);
|
||||||
|
|
||||||
|
int clen;
|
||||||
|
char cmsg[CLEN];
|
||||||
|
build_cmsg(&connection->fds_out, cmsg, &clen);
|
||||||
|
struct msghdr msg = {
|
||||||
|
.msg_name = NULL,
|
||||||
|
.msg_namelen = 0,
|
||||||
|
.msg_iov = iov,
|
||||||
|
.msg_iovlen = count,
|
||||||
|
.msg_control = (clen > 0) ? cmsg : NULL,
|
||||||
|
.msg_controllen = clen,
|
||||||
|
.msg_flags = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
int len;
|
||||||
|
do {
|
||||||
|
len = sendmsg(connection->fd, &msg, MSG_NOSIGNAL | MSG_DONTWAIT);
|
||||||
|
if (len == -1 && errno != EINTR)
|
||||||
|
return -1;
|
||||||
|
} while (len == -1);
|
||||||
|
connection_buffer_close_fds(&connection->fds_out);
|
||||||
|
connection->out.tail += len;
|
||||||
|
}
|
||||||
|
connection->want_flush = 0;
|
||||||
|
return connection->out.head - tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
int connection_put(struct connection *connection, const void *data, size_t count) {
|
||||||
|
if (connection_buffer_size(&connection->out) + count > CONNECTION_BUFFER_SIZE) {
|
||||||
|
connection->want_flush = 1;
|
||||||
|
if (connection_flush(connection) == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connection_buffer_put(&connection->out, data, count) == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection->want_flush = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int connection_put_fd(struct connection *connection, int fd) {
|
||||||
|
if (connection_buffer_size(&connection->fds_out) == MAX_FDS_OUT * sizeof fd) {
|
||||||
|
errno = EOVERFLOW;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return connection_buffer_put(&connection->fds_out, &fd, sizeof fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
int connection_get(struct connection *connection, void *dst, size_t count) {
|
||||||
|
if (count > connection_buffer_size(&connection->in)) {
|
||||||
|
errno = EAGAIN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
connection_buffer_copy(&connection->in, dst, count);
|
||||||
|
connection_buffer_consume(&connection->in, count);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int connection_get_fd(struct connection *connection) {
|
||||||
|
int fd;
|
||||||
|
if (sizeof fd > connection_buffer_size(&connection->fds_in)) {
|
||||||
|
errno = EAGAIN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
connection_buffer_copy(&connection->fds_in, &fd, sizeof fd);
|
||||||
|
connection_buffer_consume(&connection->fds_in, sizeof fd);
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void connection_close_fds(struct connection *connection) {
|
||||||
|
connection_buffer_close_fds(&connection->fds_in);
|
||||||
|
connection_buffer_close_fds(&connection->fds_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t connection_pending(struct connection *connection) {
|
||||||
|
return connection_buffer_size(&connection->in);
|
||||||
|
}
|
||||||
|
|
||||||
|
void connection_restore(struct connection *connection, size_t count) {
|
||||||
|
connection_buffer_restore(&connection->in, count);
|
||||||
|
}
|
23
common/drm.c
Normal file
23
common/drm.c
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/sysmacros.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include "drm.h"
|
||||||
|
|
||||||
|
// From libdrm
|
||||||
|
#define DRM_IOCTL_BASE 'd'
|
||||||
|
#define DRM_IO(nr) _IO(DRM_IOCTL_BASE, nr)
|
||||||
|
#define DRM_IOCTL_SET_MASTER DRM_IO(0x1e)
|
||||||
|
#define DRM_IOCTL_DROP_MASTER DRM_IO(0x1f)
|
||||||
|
|
||||||
|
int drm_set_master(int fd) {
|
||||||
|
return ioctl(fd, DRM_IOCTL_SET_MASTER, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int drm_drop_master(int fd) {
|
||||||
|
return ioctl(fd, DRM_IOCTL_DROP_MASTER, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int dev_is_drm(dev_t device) {
|
||||||
|
return major(device) == 226;
|
||||||
|
}
|
15
common/evdev.c
Normal file
15
common/evdev.c
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#include <linux/input.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/sysmacros.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include "evdev.h"
|
||||||
|
|
||||||
|
int evdev_revoke(int fd) {
|
||||||
|
return ioctl(fd, EVIOCREVOKE, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int dev_is_evdev(dev_t device) {
|
||||||
|
return major(device) == 13;
|
||||||
|
}
|
76
common/list.c
Normal file
76
common/list.c
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "list.h"
|
||||||
|
|
||||||
|
void list_init(struct list *list) {
|
||||||
|
list->capacity = 10;
|
||||||
|
list->length = 0;
|
||||||
|
list->items = malloc(sizeof(void *) * list->capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void list_resize(struct list *list) {
|
||||||
|
if (list->length == list->capacity) {
|
||||||
|
list->capacity *= 2;
|
||||||
|
list->items = realloc(list->items, sizeof(void *) * list->capacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void list_free(struct list *list) {
|
||||||
|
list->capacity = 0;
|
||||||
|
list->length = 0;
|
||||||
|
free(list->items);
|
||||||
|
}
|
||||||
|
|
||||||
|
void list_add(struct list *list, void *item) {
|
||||||
|
list_resize(list);
|
||||||
|
list->items[list->length++] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
void list_insert(struct list *list, size_t index, void *item) {
|
||||||
|
list_resize(list);
|
||||||
|
memmove(&list->items[index + 1], &list->items[index],
|
||||||
|
sizeof(void *) * (list->length - index));
|
||||||
|
list->length++;
|
||||||
|
list->items[index] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
void list_del(struct list *list, size_t index) {
|
||||||
|
list->length--;
|
||||||
|
memmove(&list->items[index], &list->items[index + 1],
|
||||||
|
sizeof(void *) * (list->length - index));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t list_find(struct list *list, const void *item) {
|
||||||
|
for (size_t i = 0; i < list->length; i++) {
|
||||||
|
if (list->items[i] == item) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void list_concat(struct list *list, struct list *source) {
|
||||||
|
if (list->length + source->length > list->capacity) {
|
||||||
|
while (list->length + source->length > list->capacity) {
|
||||||
|
list->capacity *= 2;
|
||||||
|
}
|
||||||
|
list->items = realloc(list->items, sizeof(void *) * list->capacity);
|
||||||
|
}
|
||||||
|
memmove(&list->items[list->length], source->items, sizeof(void *) * (source->length));
|
||||||
|
list->length += source->length;
|
||||||
|
}
|
||||||
|
|
||||||
|
void list_truncate(struct list *list) {
|
||||||
|
list->length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *list_pop_front(struct list *list) {
|
||||||
|
if (list->length == 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
void *item = list->items[0];
|
||||||
|
list_del(list, 0);
|
||||||
|
return item;
|
||||||
|
}
|
80
common/log.c
Normal file
80
common/log.c
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
const long NSEC_PER_SEC = 1000000000;
|
||||||
|
|
||||||
|
static enum libseat_log_level current_log_level;
|
||||||
|
static struct timespec start_time = {-1, -1};
|
||||||
|
static bool colored = false;
|
||||||
|
|
||||||
|
static const char *verbosity_colors[] = {
|
||||||
|
[LIBSEAT_SILENT] = "",
|
||||||
|
[LIBSEAT_ERROR] = "\x1B[1;31m",
|
||||||
|
[LIBSEAT_INFO] = "\x1B[1;34m",
|
||||||
|
[LIBSEAT_DEBUG] = "\x1B[1;90m",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *verbosity_headers[] = {
|
||||||
|
[LIBSEAT_SILENT] = "",
|
||||||
|
[LIBSEAT_ERROR] = "[ERROR]",
|
||||||
|
[LIBSEAT_INFO] = "[INFO]",
|
||||||
|
[LIBSEAT_DEBUG] = "[DEBUG]",
|
||||||
|
};
|
||||||
|
|
||||||
|
static void timespec_sub(struct timespec *r, const struct timespec *a, const struct timespec *b) {
|
||||||
|
r->tv_sec = a->tv_sec - b->tv_sec;
|
||||||
|
r->tv_nsec = a->tv_nsec - b->tv_nsec;
|
||||||
|
if (r->tv_nsec < 0) {
|
||||||
|
r->tv_sec--;
|
||||||
|
r->tv_nsec += NSEC_PER_SEC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void libseat_log_init(enum libseat_log_level level) {
|
||||||
|
if (start_time.tv_sec >= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &start_time);
|
||||||
|
current_log_level = level;
|
||||||
|
colored = isatty(STDERR_FILENO);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _libseat_logf(enum libseat_log_level level, const char *fmt, ...) {
|
||||||
|
int stored_errno = errno;
|
||||||
|
va_list args;
|
||||||
|
if (level < current_log_level) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct timespec ts = {0};
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
timespec_sub(&ts, &ts, &start_time);
|
||||||
|
unsigned c = (level < LIBSEAT_LOG_LEVEL_LAST) ? level : LIBSEAT_LOG_LEVEL_LAST - 1;
|
||||||
|
|
||||||
|
const char *prefix, *postfix;
|
||||||
|
|
||||||
|
if (colored) {
|
||||||
|
prefix = verbosity_colors[c];
|
||||||
|
postfix = "\x1B[0m\n";
|
||||||
|
} else {
|
||||||
|
prefix = verbosity_headers[c];
|
||||||
|
postfix = "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "%02d:%02d:%02d.%03ld %s ", (int)(ts.tv_sec / 60 / 60),
|
||||||
|
(int)(ts.tv_sec / 60 % 60), (int)(ts.tv_sec % 60), ts.tv_nsec / 1000000, prefix);
|
||||||
|
|
||||||
|
va_start(args, fmt);
|
||||||
|
vfprintf(stderr, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
fprintf(stderr, "%s", postfix);
|
||||||
|
errno = stored_errno;
|
||||||
|
}
|
202
common/terminal.c
Normal file
202
common/terminal.c
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
#include "string.h"
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <linux/kd.h>
|
||||||
|
#include <linux/vt.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
#include "terminal.h"
|
||||||
|
|
||||||
|
#define TTYPATHLEN 64
|
||||||
|
|
||||||
|
int terminal_current_vt(void) {
|
||||||
|
struct vt_stat st;
|
||||||
|
int fd = open("/dev/tty0", O_RDWR | O_NOCTTY);
|
||||||
|
if (fd == -1) {
|
||||||
|
log_errorf("could not open tty0: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int res = ioctl(fd, VT_GETSTATE, &st);
|
||||||
|
close(fd);
|
||||||
|
if (res == -1) {
|
||||||
|
log_errorf("could not retrieve VT state: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return st.v_active;
|
||||||
|
}
|
||||||
|
|
||||||
|
int terminal_setup(int vt) {
|
||||||
|
log_debugf("setting up vt %d", vt);
|
||||||
|
if (vt == -1) {
|
||||||
|
vt = 0;
|
||||||
|
}
|
||||||
|
char path[TTYPATHLEN];
|
||||||
|
if (snprintf(path, TTYPATHLEN, "/dev/tty%d", vt) == -1) {
|
||||||
|
log_errorf("could not generate tty path: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int fd = open(path, O_RDWR | O_NOCTTY);
|
||||||
|
if (fd == -1) {
|
||||||
|
log_errorf("could not open target tty: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct vt_mode mode = {
|
||||||
|
.mode = VT_PROCESS,
|
||||||
|
.waitv = 0,
|
||||||
|
.relsig = SIGUSR1,
|
||||||
|
.acqsig = SIGUSR2,
|
||||||
|
.frsig = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
int res = ioctl(fd, VT_SETMODE, &mode);
|
||||||
|
close(fd);
|
||||||
|
if (res == -1) {
|
||||||
|
log_errorf("could not set VT mode: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int terminal_teardown(int vt) {
|
||||||
|
log_debugf("tearing down vt %d", vt);
|
||||||
|
if (vt == -1) {
|
||||||
|
vt = 0;
|
||||||
|
}
|
||||||
|
char path[TTYPATHLEN];
|
||||||
|
if (snprintf(path, TTYPATHLEN, "/dev/tty%d", vt) == -1) {
|
||||||
|
log_errorf("could not generate tty path: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int fd = open(path, O_RDWR | O_NOCTTY);
|
||||||
|
if (fd == -1) {
|
||||||
|
log_errorf("could not open target tty: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (ioctl(fd, KDSETMODE, KD_TEXT) == -1) {
|
||||||
|
log_errorf("could not set KD graphics mode: %s", strerror(errno));
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (ioctl(fd, KDSKBMODE, K_UNICODE) == -1) {
|
||||||
|
log_errorf("could not set KD keyboard mode: %s", strerror(errno));
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct vt_mode mode = {
|
||||||
|
.mode = VT_PROCESS,
|
||||||
|
.waitv = 0,
|
||||||
|
.relsig = SIGUSR1,
|
||||||
|
.acqsig = SIGUSR2,
|
||||||
|
.frsig = 0,
|
||||||
|
};
|
||||||
|
if (ioctl(fd, VT_SETMODE, &mode) == -1) {
|
||||||
|
log_errorf("could not set VT mode: %s", strerror(errno));
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int terminal_switch_vt(int vt) {
|
||||||
|
log_debugf("switching to vt %d", vt);
|
||||||
|
int fd = open("/dev/tty0", O_RDWR | O_NOCTTY);
|
||||||
|
if (fd == -1) {
|
||||||
|
log_errorf("could not open tty0: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct vt_mode mode = {
|
||||||
|
.mode = VT_PROCESS,
|
||||||
|
.waitv = 0,
|
||||||
|
.relsig = SIGUSR1,
|
||||||
|
.acqsig = SIGUSR2,
|
||||||
|
.frsig = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ioctl(fd, VT_SETMODE, &mode) == -1) {
|
||||||
|
log_errorf("could not set VT mode: %s", strerror(errno));
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ioctl(fd, VT_ACTIVATE, vt) == -1) {
|
||||||
|
log_errorf("could not activate VT: %s", strerror(errno));
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int terminal_ack_switch(void) {
|
||||||
|
log_debug("acking vt switch");
|
||||||
|
int fd = open("/dev/tty0", O_RDWR | O_NOCTTY);
|
||||||
|
if (fd == -1) {
|
||||||
|
log_errorf("could not open tty0: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int res = ioctl(fd, VT_RELDISP, VT_ACKACQ);
|
||||||
|
close(fd);
|
||||||
|
if (res == -1) {
|
||||||
|
log_errorf("could not ack VT switch: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int terminal_set_keyboard(int vt, bool enable) {
|
||||||
|
log_debugf("setting KD keyboard state to %d on vt %d", enable, vt);
|
||||||
|
if (vt == -1) {
|
||||||
|
vt = 0;
|
||||||
|
}
|
||||||
|
char path[TTYPATHLEN];
|
||||||
|
if (snprintf(path, TTYPATHLEN, "/dev/tty%d", vt) == -1) {
|
||||||
|
log_errorf("could not generate tty path: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int fd = open(path, O_RDWR | O_NOCTTY);
|
||||||
|
if (fd == -1) {
|
||||||
|
log_errorf("could not generate tty path: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int res = ioctl(fd, KDSKBMODE, enable ? K_UNICODE : K_OFF);
|
||||||
|
close(fd);
|
||||||
|
if (res == -1) {
|
||||||
|
log_errorf("could not set KD keyboard mode: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int terminal_set_graphics(int vt, bool enable) {
|
||||||
|
log_debugf("setting KD graphics state to %d on vt %d", enable, vt);
|
||||||
|
if (vt == -1) {
|
||||||
|
vt = 0;
|
||||||
|
}
|
||||||
|
char path[TTYPATHLEN];
|
||||||
|
if (snprintf(path, TTYPATHLEN, "/dev/tty%d", vt) == -1) {
|
||||||
|
log_errorf("could not generate tty path: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int fd = open(path, O_RDWR | O_NOCTTY);
|
||||||
|
if (fd == -1) {
|
||||||
|
log_errorf("could not generate tty path: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int res = ioctl(fd, KDSETMODE, enable ? KD_GRAPHICS : KD_TEXT);
|
||||||
|
close(fd);
|
||||||
|
if (res == -1) {
|
||||||
|
log_errorf("could not set KD graphics mode: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
60
examples/simpletest/main.c
Normal file
60
examples/simpletest/main.c
Normal file
|
@ -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;
|
||||||
|
}
|
32
include/backend.h
Normal file
32
include/backend.h
Normal file
|
@ -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
|
37
include/client.h
Normal file
37
include/client.h
Normal file
|
@ -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
|
18
include/compiler.h
Normal file
18
include/compiler.h
Normal file
|
@ -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
|
36
include/connection.h
Normal file
36
include/connection.h
Normal file
|
@ -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
|
10
include/drm.h
Normal file
10
include/drm.h
Normal file
|
@ -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
|
9
include/evdev.h
Normal file
9
include/evdev.h
Normal file
|
@ -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
|
140
include/libseat.h
Normal file
140
include/libseat.h
Normal file
|
@ -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
|
22
include/list.h
Normal file
22
include/list.h
Normal file
|
@ -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
|
51
include/log.h
Normal file
51
include/log.h
Normal file
|
@ -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
|
137
include/poller.h
Normal file
137
include/poller.h
Normal file
|
@ -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
|
64
include/protocol.h
Normal file
64
include/protocol.h
Normal file
|
@ -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
|
47
include/seat.h
Normal file
47
include/seat.h
Normal file
|
@ -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
|
26
include/server.h
Normal file
26
include/server.h
Normal file
|
@ -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
|
14
include/terminal.h
Normal file
14
include/terminal.h
Normal file
|
@ -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
|
782
libseat/backend/logind.c
Normal file
782
libseat/backend/logind.c
Normal file
|
@ -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,
|
||||||
|
};
|
545
libseat/backend/seatd.c
Normal file
545
libseat/backend/seatd.c
Normal file
|
@ -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
|
111
libseat/libseat.c
Normal file
111
libseat/libseat.c
Normal file
|
@ -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);
|
||||||
|
}
|
157
meson.build
Normal file
157
meson.build
Normal file
|
@ -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
|
5
meson_options.txt
Normal file
5
meson_options.txt
Normal file
|
@ -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')
|
484
seatd/client.c
Normal file
484
seatd/client.c
Normal file
|
@ -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;
|
||||||
|
}
|
386
seatd/poll/basic_poller.c
Normal file
386
seatd/poll/basic_poller.c
Normal file
|
@ -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,
|
||||||
|
};
|
53
seatd/poll/poller.c
Normal file
53
seatd/poll/poller.c
Normal file
|
@ -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);
|
||||||
|
}
|
521
seatd/seat.c
Normal file
521
seatd/seat.c
Normal file
|
@ -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;
|
||||||
|
}
|
57
seatd/seatd.c
Normal file
57
seatd/seatd.c
Normal file
|
@ -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;
|
||||||
|
}
|
232
seatd/server.c
Normal file
232
seatd/server.c
Normal file
|
@ -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;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue