diff options
Diffstat (limited to 'seatd')
-rw-r--r-- | seatd/client.c | 484 | ||||
-rw-r--r-- | seatd/poll/basic_poller.c | 386 | ||||
-rw-r--r-- | seatd/poll/poller.c | 53 | ||||
-rw-r--r-- | seatd/seat.c | 521 | ||||
-rw-r--r-- | seatd/seatd.c | 57 | ||||
-rw-r--r-- | seatd/server.c | 232 |
6 files changed, 1733 insertions, 0 deletions
diff --git a/seatd/client.c b/seatd/client.c new file mode 100644 index 0000000..72e652c --- /dev/null +++ b/seatd/client.c @@ -0,0 +1,484 @@ +#define _GNU_SOURCE +#include <assert.h> +#include <errno.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include "client.h" +#include "log.h" +#include "poller.h" +#include "protocol.h" +#include "seat.h" +#include "server.h" +#include "terminal.h" + +static int get_peer(int fd, pid_t *pid, uid_t *uid, gid_t *gid) { + struct ucred cred; + socklen_t len = sizeof cred; + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) { + return -1; + } + *pid = cred.pid; + *uid = cred.uid; + *gid = cred.gid; + return 0; +} + +struct client *client_create(struct server *server, int client_fd) { + uid_t uid; + gid_t gid; + pid_t pid; + + if (get_peer(client_fd, &pid, &uid, &gid) == -1) { + return NULL; + } + + struct client *client = calloc(1, sizeof(struct client)); + if (client == NULL) { + return NULL; + } + + client->uid = uid; + client->gid = gid; + client->pid = pid; + client->server = server; + client->connection.fd = client_fd; + list_init(&client->devices); + return client; +} + +void client_kill(struct client *client) { + assert(client); + if (client->connection.fd != -1) { + shutdown(client->connection.fd, SHUT_RDWR); + close(client->connection.fd); + client->connection.fd = -1; + }; + if (client->seat != NULL) { + seat_remove_client(client->seat, client); + client->seat = NULL; + } +} + +void client_destroy(struct client *client) { + assert(client); + client->server = NULL; + if (client->seat != NULL) { + // This should also close and remove all devices + seat_remove_client(client->seat, client); + client->seat = NULL; + } + if (client->event_source != NULL) { + event_source_fd_destroy(client->event_source); + client->event_source = NULL; + } + if (client->connection.fd != -1) { + shutdown(client->connection.fd, SHUT_RDWR); + close(client->connection.fd); + client->connection.fd = -1; + } + connection_close_fds(&client->connection); + assert(client->devices.length == 0); + list_free(&client->devices); + free(client); +} + +static int client_flush(struct client *client) { + int ret = connection_flush(&client->connection); + if (ret == -1 && errno == EAGAIN) { + event_source_fd_update(client->event_source, EVENT_READABLE | EVENT_WRITABLE); + } else if (ret == -1) { + return -1; + } + return 0; +} + +static int client_send_error(struct client *client, int error_code) { + struct proto_server_error errmsg = { + .error_code = error_code, + }; + struct proto_header errheader = { + .opcode = SERVER_ERROR, + .size = sizeof errmsg, + }; + + if (connection_put(&client->connection, &errheader, sizeof errheader) == -1 || + connection_put(&client->connection, &errmsg, sizeof errmsg)) { + log_error("could not send error to client"); + return -1; + } + return 0; +} + +static char *client_get_seat_name(struct client *client) { + (void)client; + // TODO: Look up seat for session. + return "seat0"; +} + +static int handle_open_seat(struct client *client) { + char *seat_name = client_get_seat_name(client); + if (seat_name == NULL) { + log_error("could not get name of target seat"); + return -1; + } + + struct seat *seat = server_get_seat(client->server, seat_name); + if (seat == NULL) { + log_error("unable to find seat by name"); + return -1; + } + + if (seat_add_client(seat, client) == -1) { + log_errorf("unable to add client to target seat: %s", strerror(errno)); + return -1; + } + + size_t seat_name_len = strlen(seat_name); + + struct proto_server_seat_opened rmsg = { + .seat_name_len = (uint16_t)seat_name_len, + }; + struct proto_header header = { + .opcode = SERVER_SEAT_OPENED, + .size = sizeof rmsg + seat_name_len, + }; + + if (connection_put(&client->connection, &header, sizeof header) == -1 || + connection_put(&client->connection, &rmsg, sizeof rmsg) == -1 || + connection_put(&client->connection, seat_name, seat_name_len) == -1) { + log_errorf("unable to write response: %s", strerror(errno)); + return -1; + } + + seat_open_client(seat, client); + return 0; +} + +static int handle_close_seat(struct client *client) { + if (client->seat == NULL) { + log_error("protocol error: no seat associated with client"); + return -1; + } + + if (seat_remove_client(client->seat, client) == -1) { + log_error("unable to remove client from seat"); + return -1; + } + + struct proto_header header = { + .opcode = SERVER_SEAT_CLOSED, + .size = 0, + }; + + if (connection_put(&client->connection, &header, sizeof header) == -1) { + log_errorf("unable to write response: %s", strerror(errno)); + return -1; + } + + return 0; +} + +static int handle_open_device(struct client *client, char *path) { + if (client->seat == NULL) { + log_error("protocol error: no seat associated with client"); + return -1; + } + + struct seat_device *device = seat_open_device(client, path); + if (device == NULL) { + log_errorf("could not open device: %s", strerror(errno)); + goto fail; + } + + int dupfd = dup(device->fd); + if (dupfd == -1) { + log_errorf("could not dup fd: %s", strerror(errno)); + seat_close_device(client, device); + goto fail; + } + + if (connection_put_fd(&client->connection, dupfd) == -1) { + log_errorf("unable to queue fd for sending: %s", strerror(errno)); + return -1; + } + + struct proto_server_device_opened msg = { + .device_id = device->device_id, + }; + struct proto_header header = { + .opcode = SERVER_DEVICE_OPENED, + .size = sizeof msg, + }; + + if (connection_put(&client->connection, &header, sizeof header) == -1 || + connection_put(&client->connection, &msg, sizeof msg)) { + log_errorf("unable to write response: %s", strerror(errno)); + return -1; + } + + return 0; + +fail: + return client_send_error(client, errno); +} + +static int handle_close_device(struct client *client, int device_id) { + if (client->seat == NULL) { + log_error("protocol error: no seat associated with client"); + return -1; + } + + struct seat_device *device = seat_find_device(client, device_id); + if (device == NULL) { + log_error("no such device"); + errno = EBADF; + goto fail; + } + + if (seat_close_device(client, device) == -1) { + log_errorf("could not close device: %s", strerror(errno)); + goto fail; + } + + struct proto_server_device_closed msg = { + .device_id = device_id, + }; + struct proto_header header = { + .opcode = SERVER_DEVICE_CLOSED, + .size = sizeof msg, + }; + + if (connection_put(&client->connection, &header, sizeof header) == -1 || + connection_put(&client->connection, &msg, sizeof msg)) { + log_errorf("unable to write response: %s", strerror(errno)); + return -1; + } + + return 0; + +fail: + return client_send_error(client, errno); +} + +static int handle_switch_session(struct client *client, int session) { + if (client->seat == NULL) { + log_error("protocol error: no seat associated with client"); + return -1; + } + + struct seat *seat = client->seat; + if (seat->active_client != client) { + log_info("refusing to switch session: client requesting switch is not active"); + errno = EPERM; + goto error; + } + if (session <= 0) { + log_errorf("invalid session: %d", session); + errno = EINVAL; + goto error; + } + + if (client_get_session(client) == session) { + return 0; + } + + if (seat_set_next_session(seat, session) == -1) { + log_infof("could not queue session switch: %s", strerror(errno)); + goto error; + } + + return 0; + +error: + return client_send_error(client, errno); +} + +static int handle_disable_seat(struct client *client) { + if (client->seat == NULL) { + log_error("protocol error: no seat associated with client"); + return -1; + } + + struct seat *seat = client->seat; + if (seat->active_client != client) { + log_info("client is not currently active"); + errno = EPERM; + goto error; + } + + if (seat_close_client(seat, client) == -1) { + goto error; + } + + return 0; + +error: + return client_send_error(client, errno); +} + +static int client_handle_opcode(struct client *client, uint16_t opcode, size_t size) { + int res = 0; + switch (opcode) { + case CLIENT_OPEN_SEAT: { + if (size != 0) { + log_error("protocol error: invalid open_seat message"); + return -1; + } + res = handle_open_seat(client); + break; + } + case CLIENT_CLOSE_SEAT: { + if (size != 0) { + log_error("protocol error: invalid close_seat message"); + return -1; + } + res = handle_close_seat(client); + break; + } + case CLIENT_OPEN_DEVICE: { + char path[MAX_PATH_LEN]; + struct proto_client_open_device msg; + if (sizeof msg > size || connection_get(&client->connection, &msg, sizeof msg) == -1 || + sizeof msg + msg.path_len > size || msg.path_len > MAX_PATH_LEN) { + log_error("protocol error: invalid open_device message"); + return -1; + } + if (connection_get(&client->connection, path, msg.path_len) == -1) { + log_error("protocol error: invalid open_device message"); + return -1; + } + + res = handle_open_device(client, path); + break; + } + case CLIENT_CLOSE_DEVICE: { + struct proto_client_close_device msg; + if (sizeof msg > size || connection_get(&client->connection, &msg, sizeof msg) == -1) { + log_error("protocol error: invalid close_device message"); + return -1; + } + + res = handle_close_device(client, msg.device_id); + break; + } + case CLIENT_SWITCH_SESSION: { + struct proto_client_switch_session msg; + if (sizeof msg > size || connection_get(&client->connection, &msg, sizeof msg) == -1) { + log_error("protocol error: invalid switch_session message"); + return -1; + } + + res = handle_switch_session(client, msg.session); + break; + } + case CLIENT_DISABLE_SEAT: { + if (size != 0) { + log_error("protocol error: invalid disable_seat message"); + return -1; + } + res = handle_disable_seat(client); + break; + } + default: + log_errorf("protocol error: unknown opcode: %d", opcode); + res = -1; + break; + } + if (res != -1) { + res = client_flush(client); + } + return res; +} + +int client_disable_seat(struct client *client) { + struct proto_header header = { + .opcode = SERVER_DISABLE_SEAT, + .size = 0, + }; + if (connection_put(&client->connection, &header, sizeof header) == -1 || + connection_flush(&client->connection) == -1) { + log_error("unable to send event"); + return -1; + } + return 0; +} + +int client_enable_seat(struct client *client) { + struct proto_header header = { + .opcode = SERVER_ENABLE_SEAT, + .size = 0, + }; + if (connection_put(&client->connection, &header, sizeof header) == -1 || + connection_flush(&client->connection) == -1) { + log_error("unable to send event"); + return -1; + } + return 0; +} + +int client_handle_connection(int fd, uint32_t mask, void *data) { + (void)fd; + + struct client *client = data; + if (mask & EVENT_ERROR) { + log_error("connection error"); + goto fail; + } + if (mask & EVENT_HANGUP) { + log_info("client disconnected"); + goto fail; + } + + if (mask & EVENT_WRITABLE) { + int len = connection_flush(&client->connection); + if (len == -1 && errno != EAGAIN) { + log_error("could not flush client connection"); + goto fail; + } else if (len >= 0) { + event_source_fd_update(client->event_source, EVENT_READABLE); + } + } + + if (mask & EVENT_READABLE) { + int len = connection_read(&client->connection); + if (len == 0 || (len == -1 && errno != EAGAIN)) { + log_error("could not read client connection"); + goto fail; + } + + struct proto_header header; + while (connection_get(&client->connection, &header, sizeof header) != -1) { + if (connection_pending(&client->connection) < header.size) { + connection_restore(&client->connection, sizeof header); + break; + } + if (client_handle_opcode(client, header.opcode, header.size) == -1) { + goto fail; + } + } + } + + return 0; + +fail: + client_destroy(client); + return -1; +} + +int client_get_session(struct client *client) { + if (client->seat == NULL || client->seat->active_client != client) { + return -1; + } + if (client->seat->vt_bound) { + return client->seat->active_client->seat_vt; + } + // TODO: Store some session sequence + abort(); + return -1; +} diff --git a/seatd/poll/basic_poller.c b/seatd/poll/basic_poller.c new file mode 100644 index 0000000..a388d8e --- /dev/null +++ b/seatd/poll/basic_poller.c @@ -0,0 +1,386 @@ +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <signal.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "list.h" +#include "poller.h" + +struct basic_poller *global_poller = NULL; + +const struct poll_impl basic_poller_impl; +const struct event_source_fd_impl basic_poller_fd_impl; +const struct event_source_signal_impl basic_poller_signal_impl; + +struct basic_poller { + struct poller base; + struct list signals; + struct list new_signals; + struct list fds; + struct list new_fds; + + struct pollfd *pollfds; + size_t pollfds_len; + bool dirty; + bool inpoll; +}; + +struct basic_poller_fd { + struct event_source_fd base; + struct basic_poller *poller; + bool killed; +}; + +struct basic_poller_signal { + struct event_source_signal base; + struct basic_poller *poller; + bool raised; + bool killed; +}; + +static struct basic_poller *basic_poller_from_poller(struct poller *base) { + assert(base->impl == &basic_poller_impl); + return (struct basic_poller *)base; +} + +static struct poller *basic_poller_create(void) { + if (global_poller != NULL) { + errno = EEXIST; + return NULL; + } + + struct basic_poller *poller = calloc(1, sizeof(struct basic_poller)); + if (poller == NULL) { + errno = ENOMEM; + return NULL; + } + list_init(&poller->fds); + list_init(&poller->new_fds); + list_init(&poller->signals); + list_init(&poller->new_signals); + poller->base.impl = &basic_poller_impl; + global_poller = poller; + return (struct poller *)poller; +} + +static int destroy(struct poller *base) { + struct basic_poller *poller = basic_poller_from_poller(base); + for (size_t idx = 0; idx < poller->fds.length; idx++) { + struct basic_poller_fd *bpfd = poller->fds.items[idx]; + free(bpfd); + } + list_free(&poller->fds); + for (size_t idx = 0; idx < poller->new_fds.length; idx++) { + struct basic_poller_fd *bpfd = poller->new_fds.items[idx]; + free(bpfd); + } + list_free(&poller->new_fds); + for (size_t idx = 0; idx < poller->signals.length; idx++) { + struct basic_poller_signal *bps = poller->signals.items[idx]; + + struct sigaction sa; + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(bps->base.signal, &sa, NULL); + + free(bps); + } + list_free(&poller->signals); + for (size_t idx = 0; idx < poller->new_signals.length; idx++) { + struct basic_poller_signal *bps = poller->new_signals.items[idx]; + + struct sigaction sa; + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(bps->base.signal, &sa, NULL); + + free(bps); + } + list_free(&poller->new_signals); + free(poller->pollfds); + return 0; +} + +static int event_mask_to_poll_mask(uint32_t event_mask) { + int poll_mask = 0; + if (event_mask & EVENT_READABLE) { + poll_mask |= POLLIN; + } + if (event_mask & EVENT_WRITABLE) { + poll_mask |= POLLOUT; + } + return poll_mask; +} + +static uint32_t poll_mask_to_event_mask(int poll_mask) { + uint32_t event_mask = 0; + if (poll_mask & POLLIN) { + event_mask |= EVENT_READABLE; + } + if (poll_mask & POLLOUT) { + event_mask |= EVENT_WRITABLE; + } + if (poll_mask & POLLERR) { + event_mask |= EVENT_ERROR; + } + if (poll_mask & POLLHUP) { + event_mask |= EVENT_HANGUP; + } + return event_mask; +} + +static int regenerate_pollfds(struct basic_poller *poller) { + if (poller->pollfds_len != poller->fds.length) { + struct pollfd *fds = calloc(poller->fds.length, sizeof(struct pollfd)); + if (fds == NULL) { + return -1; + } + free(poller->pollfds); + poller->pollfds = fds; + poller->pollfds_len = poller->fds.length; + } + + for (size_t idx = 0; idx < poller->fds.length; idx++) { + struct basic_poller_fd *bpfd = poller->fds.items[idx]; + poller->pollfds[idx] = (struct pollfd){ + .fd = bpfd->base.fd, + .events = event_mask_to_poll_mask(bpfd->base.mask), + }; + } + + return 0; +} + +static struct event_source_fd *add_fd(struct poller *base, int fd, uint32_t mask, + event_source_fd_func_t func, void *data) { + struct basic_poller *poller = basic_poller_from_poller(base); + + struct basic_poller_fd *bpfd = calloc(1, sizeof(struct basic_poller_fd)); + if (bpfd == NULL) { + return NULL; + } + bpfd->base.impl = &basic_poller_fd_impl; + bpfd->base.fd = fd; + bpfd->base.mask = mask; + bpfd->base.data = data; + bpfd->base.func = func; + bpfd->poller = poller; + poller->dirty = true; + if (poller->inpoll) { + list_add(&poller->new_fds, bpfd); + } else { + list_add(&poller->fds, bpfd); + regenerate_pollfds(poller); + } + return (struct event_source_fd *)bpfd; +} + +static int fd_destroy(struct event_source_fd *event_source) { + struct basic_poller_fd *bpfd = (struct basic_poller_fd *)event_source; + struct basic_poller *poller = bpfd->poller; + int idx = list_find(&poller->fds, event_source); + if (idx == -1) { + return -1; + } + poller->dirty = true; + if (poller->inpoll) { + bpfd->killed = true; + } else { + list_del(&poller->fds, idx); + free(bpfd); + regenerate_pollfds(poller); + } + return 0; +} + +static int fd_update(struct event_source_fd *event_source, uint32_t mask) { + struct basic_poller_fd *bpfd = (struct basic_poller_fd *)event_source; + struct basic_poller *poller = bpfd->poller; + event_source->mask = mask; + + poller->dirty = true; + if (!poller->inpoll) { + regenerate_pollfds(poller); + } + return 0; +} + +static void signal_handler(int sig) { + if (global_poller == NULL) { + return; + } + + for (size_t idx = 0; idx < global_poller->signals.length; idx++) { + struct basic_poller_signal *bps = global_poller->signals.items[idx]; + if (bps->base.signal == sig) { + bps->raised = true; + } + } +} + +static struct event_source_signal *add_signal(struct poller *base, int signal, + event_source_signal_func_t func, void *data) { + struct basic_poller *poller = basic_poller_from_poller(base); + + struct basic_poller_signal *bps = calloc(1, sizeof(struct basic_poller_signal)); + if (bps == NULL) { + return NULL; + } + + int refcnt = 0; + for (size_t idx = 0; idx < poller->signals.length; idx++) { + struct basic_poller_signal *bps = poller->signals.items[idx]; + if (bps->base.signal == signal) { + refcnt++; + } + } + + bps->base.impl = &basic_poller_signal_impl; + bps->base.signal = signal; + bps->base.data = data; + bps->base.func = func; + bps->poller = poller; + + if (refcnt == 0) { + struct sigaction sa; + sa.sa_handler = &signal_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(signal, &sa, NULL); + } + + if (poller->inpoll) { + list_add(&poller->new_signals, bps); + } else { + list_add(&poller->signals, bps); + } + + return (struct event_source_signal *)bps; +} + +static int signal_destroy(struct event_source_signal *event_source) { + struct basic_poller_signal *bps = (struct basic_poller_signal *)event_source; + struct basic_poller *poller = bps->poller; + + int idx = list_find(&poller->signals, event_source); + if (idx == -1) { + return -1; + } + + int refcnt = 0; + for (size_t idx = 0; idx < poller->signals.length; idx++) { + struct basic_poller_signal *b = poller->signals.items[idx]; + if (b->base.signal == bps->base.signal) { + refcnt++; + } + } + + if (refcnt == 0) { + struct sigaction sa; + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(bps->base.signal, &sa, NULL); + } + + if (poller->inpoll) { + bps->killed = true; + } else { + list_del(&poller->signals, idx); + free(bps); + } + return 0; +} + +static int basic_poller_poll(struct poller *base) { + struct basic_poller *poller = basic_poller_from_poller(base); + + if (poll(poller->pollfds, poller->fds.length, -1) == -1 && errno != EINTR) { + return -1; + } + + poller->inpoll = true; + + for (size_t idx = 0; idx < poller->fds.length; idx++) { + short revents = poller->pollfds[idx].revents; + if (revents == 0) { + continue; + } + struct basic_poller_fd *bpfd = poller->fds.items[idx]; + bpfd->base.func(poller->pollfds[idx].fd, poll_mask_to_event_mask(revents), + bpfd->base.data); + } + + for (size_t idx = 0; idx < poller->signals.length; idx++) { + struct basic_poller_signal *bps = poller->signals.items[idx]; + if (!bps->raised) { + continue; + } + bps->base.func(bps->base.signal, bps->base.data); + bps->raised = false; + } + + poller->inpoll = false; + + for (size_t idx = 0; idx < poller->fds.length; idx++) { + struct basic_poller_fd *bpfd = poller->fds.items[idx]; + if (!bpfd->killed) { + continue; + } + + list_del(&poller->fds, idx); + free(bpfd); + idx--; + } + + for (size_t idx = 0; idx < poller->signals.length; idx++) { + struct basic_poller_signal *bps = poller->signals.items[idx]; + if (!bps->killed) { + continue; + } + + list_del(&poller->signals, idx); + free(bps); + idx--; + } + + if (poller->new_fds.length > 0) { + list_concat(&poller->fds, &poller->new_fds); + list_truncate(&poller->new_fds); + } + + if (poller->new_signals.length > 0) { + list_concat(&poller->signals, &poller->new_signals); + list_truncate(&poller->new_signals); + } + + if (poller->dirty) { + regenerate_pollfds(poller); + poller->dirty = false; + } + + return 0; +} + +const struct event_source_fd_impl basic_poller_fd_impl = { + .update = fd_update, + .destroy = fd_destroy, +}; + +const struct event_source_signal_impl basic_poller_signal_impl = { + .destroy = signal_destroy, +}; + +const struct poll_impl basic_poller_impl = { + .create = basic_poller_create, + .destroy = destroy, + .add_fd = add_fd, + .add_signal = add_signal, + .poll = basic_poller_poll, +}; diff --git a/seatd/poll/poller.c b/seatd/poll/poller.c new file mode 100644 index 0000000..db39bc0 --- /dev/null +++ b/seatd/poll/poller.c @@ -0,0 +1,53 @@ +#include "poller.h" +#include <assert.h> + +extern const struct poll_impl basic_poller_impl; + +struct poller *poller_create(void) { + // TODO: Other poll impls + return basic_poller_impl.create(); +} + +int poller_destroy(struct poller *poller) { + assert(poller); + assert(poller->impl); + return poller->impl->destroy(poller); +} + +struct event_source_fd *poller_add_fd(struct poller *poller, int fd, uint32_t mask, + event_source_fd_func_t func, void *data) { + assert(poller); + assert(poller->impl); + return poller->impl->add_fd(poller, fd, mask, func, data); +} + +int event_source_fd_destroy(struct event_source_fd *event_source) { + assert(event_source); + assert(event_source->impl); + return event_source->impl->destroy(event_source); +} + +struct event_source_signal *poller_add_signal(struct poller *poller, int signal, + event_source_signal_func_t func, void *data) { + assert(poller); + assert(poller->impl); + return poller->impl->add_signal(poller, signal, func, data); +} + +int event_source_signal_destroy(struct event_source_signal *event_source) { + assert(event_source); + assert(event_source->impl); + return event_source->impl->destroy(event_source); +} + +int event_source_fd_update(struct event_source_fd *event_source, uint32_t mask) { + assert(event_source); + assert(event_source->impl); + return event_source->impl->update(event_source, mask); +} + +int poller_poll(struct poller *poller) { + assert(poller); + assert(poller->impl); + return poller->impl->poll(poller); +} diff --git a/seatd/seat.c b/seatd/seat.c new file mode 100644 index 0000000..ab0e338 --- /dev/null +++ b/seatd/seat.c @@ -0,0 +1,521 @@ +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "client.h" +#include "drm.h" +#include "evdev.h" +#include "list.h" +#include "log.h" +#include "protocol.h" +#include "seat.h" +#include "terminal.h" + +struct seat *seat_create(const char *seat_name, bool vt_bound) { + struct seat *seat = calloc(1, sizeof(struct seat)); + if (seat == NULL) { + return NULL; + } + list_init(&seat->clients); + seat->vt_bound = vt_bound; + + seat->seat_name = strdup(seat_name); + if (seat->seat_name == NULL) { + free(seat); + return NULL; + } + + log_debugf("created seat '%s' (vt_bound: %d)", seat_name, vt_bound); + return seat; +} + +void seat_destroy(struct seat *seat) { + assert(seat); + while (seat->clients.length > 0) { + struct client *client = seat->clients.items[seat->clients.length - 1]; + // This will cause the client to remove itself from the seat + assert(client->seat); + client_kill(client); + } + + free(seat->seat_name); + free(seat); +} + +int seat_add_client(struct seat *seat, struct client *client) { + assert(seat); + assert(client); + + if (client->seat != NULL) { + log_error("cannot add client: client is already a member of a seat"); + return -1; + } + + if (seat->vt_bound && seat->active_client != NULL) { + log_error("cannot add client: seat is vt_bound and an active client already exists"); + return -1; + } + + client->seat = seat; + + list_add(&seat->clients, client); + log_debug("added client"); + return 0; +} + +int seat_remove_client(struct seat *seat, struct client *client) { + assert(seat); + assert(client); + assert(client->seat == seat); + + // We must first remove the client to avoid reactivation + bool found = false; + for (size_t idx = 0; idx < seat->clients.length; idx++) { + struct client *c = seat->clients.items[idx]; + if (client == c) { + list_del(&seat->clients, idx); + found = true; + break; + } + } + + if (!found) { + log_debug("client was not on the client list"); + } + + if (seat->next_client == client) { + seat->next_client = NULL; + } + + while (client->devices.length > 0) { + struct seat_device *device = client->devices.items[client->devices.length - 1]; + seat_close_device(client, device); + } + + if (seat->active_client == client) { + seat_close_client(seat, client); + } + + client->seat = NULL; + log_debug("removed client"); + + return found ? -1 : 0; +} + +struct seat_device *seat_find_device(struct client *client, int device_id) { + assert(client); + assert(client->seat); + assert(device_id != 0); + + for (size_t idx = 0; idx < client->devices.length; idx++) { + struct seat_device *seat_device = client->devices.items[idx]; + if (seat_device->device_id == device_id) { + return seat_device; + } + } + errno = ENOENT; + return NULL; +} + +struct seat_device *seat_open_device(struct client *client, const char *path) { + assert(client); + assert(client->seat); + assert(strlen(path) > 0); + struct seat *seat = client->seat; + + if (client != seat->active_client) { + errno = EPERM; + return NULL; + } + + char sanitized_path[MAX_PATH_LEN]; + if (realpath(path, sanitized_path) == NULL) { + log_errorf("invalid path '%s': %s", path, strerror(errno)); + return NULL; + } + + int device_id = 1; + for (size_t idx = 0; idx < client->devices.length; idx++) { + struct seat_device *device = client->devices.items[idx]; + + // If the device already exists, increase the ref count and + // return it. + if (strcmp(device->path, path) == 0) { + device->ref_cnt++; + return device; + } + + // If the device has a higher id, up our device id + if (device->device_id >= device_id) { + device_id = device->device_id + 1; + } + } + + if (client->devices.length >= MAX_SEAT_DEVICES) { + log_error("max seat devices exceeded"); + errno = EMFILE; + return NULL; + } + + const char *prefix = "/dev/"; + if (strncmp(prefix, sanitized_path, strlen(prefix)) != 0) { + log_errorf("invalid path '%s': expected device in /dev", sanitized_path); + errno = ENOENT; + return NULL; + } + + int fd = open(sanitized_path, O_RDWR | O_NOCTTY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK); + if (fd == -1) { + log_errorf("could not open file: %s", strerror(errno)); + return NULL; + } + + struct stat st; + if (fstat(fd, &st) == -1) { + log_errorf("could not fstat: %s", strerror(errno)); + close(fd); + errno = EACCES; + return NULL; + } + + if (dev_is_drm(st.st_rdev)) { + if (drm_set_master(fd) == -1) { + log_debugf("drm_set_master failed: %s", strerror(errno)); + } + } else if (dev_is_evdev(st.st_rdev)) { + // Nothing to do here + } else { + // Not a device type we want to share + log_errorf("disallowed device type for '%s': %ld", sanitized_path, st.st_rdev); + close(fd); + errno = EACCES; + return NULL; + } + + struct seat_device *device = calloc(1, sizeof(struct seat_device)); + if (device == NULL) { + log_errorf("could not alloc device for '%s': %s", sanitized_path, strerror(errno)); + close(fd); + errno = ENOMEM; + return NULL; + } + + device->path = strdup(sanitized_path); + if (device->path == NULL) { + log_errorf("could not dup path for '%s': %s", sanitized_path, strerror(errno)); + close(fd); + free(device); + return NULL; + } + + log_debugf("seat: %p, client: %p, path: '%s', device_id: %d", (void *)seat, (void *)client, + path, device_id); + + device->ref_cnt++; + device->dev = st.st_rdev; + device->fd = fd; + device->device_id = device_id; + device->active = true; + list_add(&client->devices, device); + return device; +} + +int seat_close_device(struct client *client, struct seat_device *seat_device) { + assert(client); + assert(client->seat); + assert(seat_device && seat_device->fd > 0); + + // Find the device in our list + size_t idx = list_find(&client->devices, seat_device); + if (idx == -1UL) { + log_error("seat device not registered by client"); + errno = ENOENT; + return -1; + } + + log_debugf("seat: %p, client: %p, path: '%s', device_id: %d", (void *)client->seat, + (void *)client, seat_device->path, seat_device->device_id); + + seat_device->ref_cnt--; + if (seat_device->ref_cnt > 0) { + // We still have more references to this device, so leave it be. + return 0; + } + + // The ref count hit zero, so destroy the device + list_del(&client->devices, idx); + if (seat_device->active && seat_device->fd != -1) { + if (dev_is_drm(seat_device->dev)) { + if (drm_drop_master(seat_device->fd) == -1) { + log_debugf("drm_drop_master failed: %s", strerror(errno)); + } + } else if (dev_is_evdev(seat_device->dev)) { + if (evdev_revoke(seat_device->fd) == -1) { + log_debugf("evdev_revoke failed: %s", strerror(errno)); + } + } + close(seat_device->fd); + seat_device->fd = -1; + } + free(seat_device->path); + free(seat_device); + return 0; +} + +static int seat_deactivate_device(struct client *client, struct seat_device *seat_device) { + assert(client); + assert(client->seat); + assert(seat_device && seat_device->fd > 0); + + if (!seat_device->active) { + return 0; + } + if (dev_is_drm(seat_device->dev)) { + if (drm_drop_master(seat_device->fd) == -1) { + return -1; + } + } else if (dev_is_evdev(seat_device->dev)) { + if (evdev_revoke(seat_device->fd) == -1) { + return -1; + } + } else { + errno = EACCES; + return -1; + } + seat_device->active = false; + return 0; +} + +static int seat_activate_device(struct client *client, struct seat_device *seat_device) { + assert(client); + assert(client->seat); + assert(seat_device && seat_device->fd > 0); + + if (seat_device->active) { + return 0; + } + if (dev_is_drm(seat_device->dev)) { + drm_set_master(seat_device->fd); + seat_device->active = true; + } else if (dev_is_evdev(seat_device->dev)) { + // We can't do anything here + errno = EINVAL; + return -1; + } else { + errno = EACCES; + return -1; + } + return 0; +} + +int seat_open_client(struct seat *seat, struct client *client) { + assert(seat); + assert(client); + + if (seat->vt_bound && client->seat_vt == 0) { + client->seat_vt = terminal_current_vt(); + } + + if (seat->active_client != NULL) { + log_error("client already active"); + errno = EBUSY; + return -1; + } + + if (seat->vt_bound) { + terminal_setup(client->seat_vt); + terminal_set_keyboard(client->seat_vt, false); + } + + for (size_t idx = 0; idx < client->devices.length; idx++) { + struct seat_device *device = client->devices.items[idx]; + if (seat_activate_device(client, device) == -1) { + log_errorf("unable to activate '%s': %s", device->path, strerror(errno)); + } + } + + log_debugf("activated %zd devices", client->devices.length); + + seat->active_client = client; + if (client_enable_seat(client) == -1) { + seat_remove_client(seat, client); + return -1; + } + + log_info("client successfully enabled"); + return 0; +} + +int seat_close_client(struct seat *seat, struct client *client) { + assert(seat); + assert(client); + + if (seat->active_client != client) { + log_error("client not active"); + errno = EBUSY; + return -1; + } + + // We *deactivate* all remaining fds. These may later be reactivated. + // The reason we cannot just close them is that certain device fds, such + // as for DRM, must maintain the exact same file description for their + // contexts to remain valid. + for (size_t idx = 0; idx < client->devices.length; idx++) { + struct seat_device *device = client->devices.items[idx]; + if (seat_deactivate_device(client, device) == -1) { + log_errorf("unable to deactivate '%s': %s", device->path, strerror(errno)); + } + } + + log_debugf("deactivated %zd devices", client->devices.length); + + int vt = seat->active_client->seat_vt; + seat->active_client = NULL; + + if (seat->vt_bound) { + if (seat->vt_pending_ack) { + log_debug("acking pending VT switch"); + seat->vt_pending_ack = false; + terminal_teardown(vt); + terminal_ack_switch(); + return 0; + } + } + + seat_activate(seat); + log_debug("closed client"); + return 0; +} + +int seat_set_next_session(struct seat *seat, int session) { + assert(seat); + + // Check if the session number is valid + if (session <= 0) { + errno = EINVAL; + return -1; + } + + // Check if a switch is already queued + if (seat->next_vt > 0 || seat->next_client != NULL) { + return 0; + } + + struct client *target = NULL; + for (size_t idx = 0; idx < seat->clients.length; idx++) { + struct client *c = seat->clients.items[idx]; + if (client_get_session(c) == session) { + target = c; + break; + } + } + + if (target != NULL) { + log_info("queuing switch to different client"); + seat->next_client = target; + seat->next_vt = 0; + } else if (seat->vt_bound) { + log_info("queuing switch to different VT"); + seat->next_vt = session; + seat->next_client = NULL; + } else { + log_error("no valid switch available"); + errno = EINVAL; + return -1; + } + + if (client_disable_seat(seat->active_client) == -1) { + seat_remove_client(seat, seat->active_client); + } + + return 0; +} + +int seat_activate(struct seat *seat) { + assert(seat); + + // We already have an active client! + if (seat->active_client != NULL) { + return 0; + } + + // If we're asked to do a simple VT switch, do that + if (seat->vt_bound && seat->next_vt > 0) { + log_info("executing VT switch"); + terminal_switch_vt(seat->next_vt); + seat->next_vt = 0; + return 0; + } + + int vt = -1; + if (seat->vt_bound) { + vt = terminal_current_vt(); + } + + // Try to pick a client for activation + struct client *next_client = NULL; + if (seat->next_client != NULL) { + // A specific client has been requested, use it + next_client = seat->next_client; + seat->next_client = NULL; + } else if (seat->clients.length > 0 && seat->vt_bound) { + // No client is requested, try to find an applicable one + for (size_t idx = 0; idx < seat->clients.length; idx++) { + struct client *client = seat->clients.items[idx]; + if (client->seat_vt == vt) { + next_client = client; + break; + } + } + } else if (seat->clients.length > 0) { + next_client = seat->clients.items[0]; + } + + if (next_client == NULL) { + // No suitable client found + log_info("no client suitable for activation"); + if (seat->vt_bound) { + terminal_teardown(vt); + } + return -1; + } + + log_info("activating next client"); + if (seat->vt_bound && next_client->seat_vt != vt) { + terminal_switch_vt(next_client->seat_vt); + } + + return seat_open_client(seat, next_client); +} + +int seat_prepare_vt_switch(struct seat *seat) { + assert(seat); + + if (seat->active_client == NULL) { + log_info("no active client, performing switch immediately"); + terminal_ack_switch(); + return 0; + } + + if (seat->vt_pending_ack) { + log_info("impatient user, killing session to force pending switch"); + seat_close_client(seat, seat->active_client); + return 0; + } + + log_debug("delaying VT switch acknowledgement"); + + seat->vt_pending_ack = true; + if (client_disable_seat(seat->active_client) == -1) { + seat_remove_client(seat, seat->active_client); + } + + return 0; +} diff --git a/seatd/seatd.c b/seatd/seatd.c new file mode 100644 index 0000000..cfed341 --- /dev/null +++ b/seatd/seatd.c @@ -0,0 +1,57 @@ +#include <errno.h> +#include <poll.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include "client.h" +#include "log.h" +#include "poller.h" +#include "server.h" + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + char *loglevel = getenv("SEATD_LOGLEVEL"); + enum libseat_log_level level = LIBSEAT_ERROR; + if (loglevel != NULL) { + if (strcmp(loglevel, "silent") == 0) { + level = LIBSEAT_SILENT; + } else if (strcmp(loglevel, "info") == 0) { + level = LIBSEAT_INFO; + } else if (strcmp(loglevel, "debug") == 0) { + level = LIBSEAT_DEBUG; + } + } + libseat_log_init(level); + + struct server *server = server_create(); + if (server == NULL) { + log_errorf("server_create failed: %s", strerror(errno)); + return 1; + } + char *path = getenv("SEATD_SOCK"); + if (path == NULL) { + path = "/run/seatd.sock"; + } + + if (server_listen(server, path) == -1) { + log_errorf("server_listen failed: %s", strerror(errno)); + return 1; + } + + log_info("seatd started"); + + while (server->running) { + if (poller_poll(server->poller) == -1) { + log_errorf("poller failed: %s", strerror(errno)); + return 1; + } + } + unlink(path); + return 0; +} diff --git a/seatd/server.c b/seatd/server.c new file mode 100644 index 0000000..3db9b69 --- /dev/null +++ b/seatd/server.c @@ -0,0 +1,232 @@ +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <grp.h> +#include <signal.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include <unistd.h> + +#include "client.h" +#include "list.h" +#include "log.h" +#include "poller.h" +#include "seat.h" +#include "server.h" +#include "terminal.h" + +#define LISTEN_BACKLOG 16 + +static int server_handle_vt_acq(int signal, void *data); +static int server_handle_vt_rel(int signal, void *data); +static int server_handle_kill(int signal, void *data); + +struct server *server_create(void) { + struct poller *poller = poller_create(); + if (poller == NULL) { + log_error("could not create poller"); + return NULL; + } + struct server *server = calloc(1, sizeof(struct server)); + if (server == NULL) { + return NULL; + } + server->poller = poller; + + list_init(&server->seats); + + if (poller_add_signal(poller, SIGUSR1, server_handle_vt_rel, server) == NULL || + poller_add_signal(poller, SIGUSR2, server_handle_vt_acq, server) == NULL || + poller_add_signal(poller, SIGINT, server_handle_kill, server) == NULL || + poller_add_signal(poller, SIGTERM, server_handle_kill, server) == NULL) { + server_destroy(server); + return NULL; + } + + char *vtenv = getenv("SEATD_VTBOUND"); + + // TODO: create more seats: + struct seat *seat = seat_create("seat0", vtenv == NULL || strcmp(vtenv, "1") == 0); + if (seat == NULL) { + server_destroy(server); + return NULL; + } + + list_add(&server->seats, seat); + server->running = true; + return server; +} + +void server_destroy(struct server *server) { + assert(server); + for (size_t idx = 0; idx < server->seats.length; idx++) { + struct seat *seat = server->seats.items[idx]; + seat_destroy(seat); + } + list_free(&server->seats); + if (server->poller != NULL) { + poller_destroy(server->poller); + server->poller = NULL; + } + free(server); +} + +struct seat *server_get_seat(struct server *server, const char *seat_name) { + for (size_t idx = 0; idx < server->seats.length; idx++) { + struct seat *seat = server->seats.items[idx]; + if (strcmp(seat->seat_name, seat_name) == 0) { + return seat; + } + } + return NULL; +} + +static int server_handle_vt_acq(int signal, void *data) { + (void)signal; + struct server *server = data; + struct seat *seat = server_get_seat(server, "seat0"); + if (seat == NULL) { + return -1; + } + + seat_activate(seat); + return 0; +} + +static int server_handle_vt_rel(int signal, void *data) { + (void)signal; + struct server *server = data; + struct seat *seat = server_get_seat(server, "seat0"); + if (seat == NULL) { + return -1; + } + + seat_prepare_vt_switch(seat); + return 0; +} + +static int server_handle_kill(int signal, void *data) { + (void)signal; + struct server *server = data; + server->running = false; + return 0; +} + +static int set_nonblock(int fd) { + int flags; + if ((flags = fcntl(fd, F_GETFD)) == -1 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { + log_errorf("could not set FD_CLOEXEC on socket: %s", strerror(errno)); + return -1; + } + if ((flags = fcntl(fd, F_GETFL)) == -1 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { + log_errorf("could not set O_NONBLOCK on socket: %s", strerror(errno)); + return -1; + } + return 0; +} + +static int server_handle_connection(int fd, uint32_t mask, void *data) { + struct server *server = data; + if (mask & (EVENT_ERROR | EVENT_HANGUP)) { + close(fd); + log_errorf("server socket recieved an error: %s", strerror(errno)); + exit(1); + } + + if (mask & EVENT_READABLE) { + int new_fd = accept(fd, NULL, NULL); + if (fd == -1) { + log_errorf("could not accept client connection: %s", strerror(errno)); + return 0; + } + + if (set_nonblock(new_fd) != 0) { + close(new_fd); + log_errorf("could not prepare new client socket: %s", strerror(errno)); + return 0; + } + + struct client *client = client_create(server, new_fd); + client->event_source = poller_add_fd(server->poller, new_fd, EVENT_READABLE, + client_handle_connection, client); + if (client->event_source == NULL) { + client_destroy(client); + log_errorf("could not add client socket to poller: %s", strerror(errno)); + return 0; + } + log_infof("new client connected (pid: %d, uid: %d, gid: %d)", client->pid, + client->uid, client->gid); + } + return 0; +} + +int server_add_client(struct server *server, int fd) { + if (set_nonblock(fd) != 0) { + close(fd); + log_errorf("could not prepare new client socket: %s", strerror(errno)); + return -1; + } + + struct client *client = client_create(server, fd); + client->event_source = + poller_add_fd(server->poller, fd, EVENT_READABLE, client_handle_connection, client); + if (client->event_source == NULL) { + client_destroy(client); + log_errorf("could not add client socket to poller: %s", strerror(errno)); + return -1; + } + return 0; +} + +int server_listen(struct server *server, const char *path) { + union { + struct sockaddr_un unix; + struct sockaddr generic; + } addr = {0}; + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + log_errorf("could not create socket: %s", strerror(errno)); + return -1; + } + if (set_nonblock(fd) == -1) { + close(fd); + log_errorf("could not prepare socket: %s", strerror(errno)); + return -1; + } + + addr.unix.sun_family = AF_UNIX; + strncpy(addr.unix.sun_path, path, sizeof addr.unix.sun_path - 1); + socklen_t size = offsetof(struct sockaddr_un, sun_path) + strlen(addr.unix.sun_path); + if (bind(fd, &addr.generic, size) == -1) { + log_errorf("could not bind socket: %s", strerror(errno)); + close(fd); + return -1; + } + if (listen(fd, LISTEN_BACKLOG) == -1) { + log_errorf("could not listen on socket: %s", strerror(errno)); + close(fd); + return -1; + } + struct group *videogrp = getgrnam("video"); + if (videogrp != NULL) { + if (chown(path, 0, videogrp->gr_gid) == -1) { + log_errorf("could not chown socket to video group: %s", strerror(errno)); + } else if (chmod(path, 0770) == -1) { + log_errorf("could not chmod socket: %s", strerror(errno)); + } + } else { + log_errorf("could not get video group: %s", strerror(errno)); + } + if (poller_add_fd(server->poller, fd, EVENT_READABLE, server_handle_connection, server) == + NULL) { + log_errorf("could not add socket to poller: %s", strerror(errno)); + close(fd); + return -1; + } + return 0; +} |