diff options
author | Kenny Levinsen <kl@kl.wtf> | 2020-07-31 00:22:18 +0200 |
---|---|---|
committer | Kenny Levinsen <kl@kl.wtf> | 2020-07-31 00:22:18 +0200 |
commit | 61716a2c77dfde9addf6b41a6d72d26a8584150e (patch) | |
tree | 537cd84661955497bdb304f88896e36896df4e5f /libseat | |
parent | f85434de666f10da0cbcaccdbb7d88917c5fa887 (diff) |
Initial implementation of seatd and libseat
Diffstat (limited to 'libseat')
-rw-r--r-- | libseat/backend/logind.c | 782 | ||||
-rw-r--r-- | libseat/backend/seatd.c | 545 | ||||
-rw-r--r-- | libseat/libseat.c | 111 |
3 files changed, 1438 insertions, 0 deletions
diff --git a/libseat/backend/logind.c b/libseat/backend/logind.c new file mode 100644 index 0000000..ec19bba --- /dev/null +++ b/libseat/backend/logind.c @@ -0,0 +1,782 @@ +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <sys/un.h> +#include <unistd.h> + +#if defined(HAVE_ELOGIND) +#include <elogind/sd-bus.h> +#include <elogind/sd-login.h> +#elif defined(HAVE_SYSTEMD) +#include <systemd/sd-bus.h> +#include <systemd/sd-login.h> +#else +#error logind backend requires either elogind or systemd +#endif + +#include "backend.h" +#include "drm.h" +#include "libseat.h" +#include "list.h" + +struct backend_logind { + struct libseat base; + struct libseat_seat_listener *seat_listener; + void *seat_listener_data; + + sd_bus *bus; + char *id; + char *seat; + char *path; + char *seat_path; + + bool can_graphical; + bool active; + bool initial_setup; + int has_drm; +}; + +const struct libseat_impl logind_impl; +static struct backend_logind *backend_logind_from_libseat_backend(struct libseat *base); + +static void destroy(struct backend_logind *backend) { + assert(backend); + if (backend->bus != NULL) { + sd_bus_unref(backend->bus); + } + free(backend->id); + free(backend->seat); + free(backend->path); + free(backend->seat_path); + free(backend); +} + +static int close_seat(struct libseat *base) { + struct backend_logind *backend = backend_logind_from_libseat_backend(base); + destroy(backend); + return 0; +} + +static int open_device(struct libseat *base, const char *path, int *fd) { + struct backend_logind *session = backend_logind_from_libseat_backend(base); + + int ret; + int tmpfd = -1; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + struct stat st; + if (stat(path, &st) < 0) { + return -1; + } + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", session->path, + "org.freedesktop.login1.Session", "TakeDevice", &error, &msg, "uu", + major(st.st_rdev), minor(st.st_rdev)); + if (ret < 0) { + tmpfd = -1; + goto out; + } + + int paused = 0; + ret = sd_bus_message_read(msg, "hb", &tmpfd, &paused); + if (ret < 0) { + tmpfd = -1; + goto out; + } + + // The original fd seems to be closed when the message is freed + // so we just clone it. + tmpfd = fcntl(tmpfd, F_DUPFD_CLOEXEC, 0); + if (tmpfd < 0) { + tmpfd = -1; + goto out; + } + + if (dev_is_drm(st.st_rdev)) { + session->has_drm++; + } + + *fd = tmpfd; +out: + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return tmpfd; +} + +static int close_device(struct libseat *base, int device_id) { + struct backend_logind *session = backend_logind_from_libseat_backend(base); + if (device_id < 0) { + return -1; + } + + int fd = device_id; + + struct stat st = {0}; + if (fstat(fd, &st) < 0) { + close(fd); + return -1; + } + if (dev_is_drm(st.st_rdev)) { + session->has_drm--; + assert(session->has_drm >= 0); + } + close(fd); + + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_call_method(session->bus, "org.freedesktop.login1", session->path, + "org.freedesktop.login1.Session", "ReleaseDevice", &error, &msg, "uu", + major(st.st_rdev), minor(st.st_rdev)); + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + + return -1; +} + +static int switch_session(struct libseat *base, int s) { + struct backend_logind *session = backend_logind_from_libseat_backend(base); + if (s >= UINT16_MAX || s < 0) { + return -1; + } + + // Only seat0 has VTs associated with it + if (strcmp(session->seat, "seat0") != 0) { + return true; + } + + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + "/org/freedesktop/login1/seat/seat0", "org.freedesktop.login1.Seat", + "SwitchTo", &error, &msg, "u", (uint32_t)s); + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return ret >= 0; +} + +static int disable_seat(struct libseat *base) { + (void)base; + return 0; +} + +static int get_fd(struct libseat *base) { + struct backend_logind *backend = backend_logind_from_libseat_backend(base); + return sd_bus_get_fd(backend->bus); +} + +static int poll_connection(struct backend_logind *backend, int timeout) { + struct pollfd fd = { + .fd = sd_bus_get_fd(backend->bus), + .events = POLLIN, + }; + + if (poll(&fd, 1, timeout) == -1) { + if (errno == EAGAIN || errno == EINTR) { + return 0; + } else { + return -1; + } + } + + if (fd.revents & (POLLERR | POLLHUP)) { + return -1; + } + return 0; +} + +static int dispatch_background(struct libseat *base, int timeout) { + struct backend_logind *backend = backend_logind_from_libseat_backend(base); + if (backend->initial_setup) { + backend->initial_setup = false; + if (backend->active) { + backend->seat_listener->enable_seat(&backend->base, + backend->seat_listener_data); + } else { + backend->seat_listener->disable_seat(&backend->base, + backend->seat_listener_data); + } + } + + int total_dispatched = 0; + int dispatched = 0; + while ((dispatched = sd_bus_process(backend->bus, NULL)) > 0) { + total_dispatched += dispatched; + } + if (total_dispatched == 0 && timeout != 0) { + if (poll_connection(backend, timeout) == -1) { + return -1; + } + while ((dispatched = sd_bus_process(backend->bus, NULL)) > 0) { + total_dispatched += dispatched; + } + } + return total_dispatched; +} + +static const char *seat_name(struct libseat *base) { + struct backend_logind *backend = backend_logind_from_libseat_backend(base); + + if (backend->seat == NULL) { + return NULL; + } + return backend->seat; +} + +static struct backend_logind *backend_logind_from_libseat_backend(struct libseat *base) { + assert(base->impl == &logind_impl); + return (struct backend_logind *)base; +} + +static bool session_activate(struct backend_logind *session) { + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + int ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", session->path, + "org.freedesktop.login1.Session", "Activate", &error, &msg, ""); + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return ret >= 0; +} + +static bool take_control(struct backend_logind *session) { + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + int ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", session->path, + "org.freedesktop.login1.Session", "TakeControl", &error, &msg, + "b", false); + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return ret >= 0; +} + +static void set_active(struct backend_logind *backend, bool active) { + if (backend->active == active) { + return; + } + + backend->active = active; + if (active) { + backend->seat_listener->enable_seat(&backend->base, backend->seat_listener_data); + } else { + backend->seat_listener->disable_seat(&backend->base, backend->seat_listener_data); + } +} + +static int pause_device(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { + struct backend_logind *session = userdata; + + uint32_t major, minor; + const char *type; + int ret = sd_bus_message_read(msg, "uus", &major, &minor, &type); + if (ret < 0) { + goto error; + } + + if (dev_is_drm(makedev(major, minor)) && strcmp(type, "gone") != 0) { + assert(session->has_drm > 0); + set_active(session, false); + } + + if (strcmp(type, "pause") == 0) { + sd_bus_call_method(session->bus, "org.freedesktop.login1", session->path, + "org.freedesktop.login1.Session", "PauseDeviceComplete", + ret_error, &msg, "uu", major, minor); + } + +error: + return 0; +} + +static int resume_device(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { + (void)ret_error; + struct backend_logind *session = userdata; + int ret; + + int fd; + uint32_t major, minor; + ret = sd_bus_message_read(msg, "uuh", &major, &minor, &fd); + if (ret < 0) { + goto error; + } + + if (dev_is_drm(makedev(major, minor))) { + assert(session->has_drm > 0); + set_active(session, true); + } + +error: + return 0; +} + +static int session_properties_changed(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { + (void)ret_error; + struct backend_logind *session = userdata; + int ret = 0; + + if (session->has_drm > 0) { + return 0; + } + + // PropertiesChanged arg 1: interface + const char *interface; + ret = sd_bus_message_read_basic(msg, 's', &interface); // skip path + if (ret < 0) { + goto error; + } + + if (strcmp(interface, "org.freedesktop.login1.Session") != 0) { + // not interesting for us; ignore + return 0; + } + + // PropertiesChanged arg 2: changed properties with values + ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); + if (ret < 0) { + goto error; + } + + const char *s; + while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { + ret = sd_bus_message_read_basic(msg, 's', &s); + if (ret < 0) { + goto error; + } + + if (strcmp(s, "Active") == 0) { + int ret; + ret = sd_bus_message_enter_container(msg, 'v', "b"); + if (ret < 0) { + goto error; + } + + bool active; + ret = sd_bus_message_read_basic(msg, 'b', &active); + if (ret < 0) { + goto error; + } + + set_active(session, active); + return 0; + } else { + sd_bus_message_skip(msg, "{sv}"); + } + + ret = sd_bus_message_exit_container(msg); + if (ret < 0) { + goto error; + } + } + + if (ret < 0) { + goto error; + } + + ret = sd_bus_message_exit_container(msg); + if (ret < 0) { + goto error; + } + + // PropertiesChanged arg 3: changed properties without values + sd_bus_message_enter_container(msg, 'a', "s"); + while ((ret = sd_bus_message_read_basic(msg, 's', &s)) > 0) { + if (strcmp(s, "Active") == 0) { + sd_bus_error error = SD_BUS_ERROR_NULL; + bool active; + ret = sd_bus_get_property_trivial(session->bus, "org.freedesktop.login1", + session->path, + "org.freedesktop.login1.Session", + "Active", &error, 'b', &active); + if (ret < 0) { + return 0; + } + + set_active(session, active); + return 0; + } + } + + if (ret < 0) { + goto error; + } + + return 0; + +error: + return 0; +} + +static int seat_properties_changed(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { + (void)ret_error; + struct backend_logind *session = userdata; + int ret = 0; + + if (session->has_drm > 0) { + return 0; + } + + // PropertiesChanged arg 1: interface + const char *interface; + ret = sd_bus_message_read_basic(msg, 's', &interface); // skip path + if (ret < 0) { + goto error; + } + + if (strcmp(interface, "org.freedesktop.login1.Seat") != 0) { + // not interesting for us; ignore + return 0; + } + + // PropertiesChanged arg 2: changed properties with values + ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); + if (ret < 0) { + goto error; + } + + const char *s; + while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { + ret = sd_bus_message_read_basic(msg, 's', &s); + if (ret < 0) { + goto error; + } + + if (strcmp(s, "CanGraphical") == 0) { + int ret; + ret = sd_bus_message_enter_container(msg, 'v', "b"); + if (ret < 0) { + goto error; + } + + ret = sd_bus_message_read_basic(msg, 'b', &session->can_graphical); + if (ret < 0) { + goto error; + } + + return 0; + } else { + sd_bus_message_skip(msg, "{sv}"); + } + + ret = sd_bus_message_exit_container(msg); + if (ret < 0) { + goto error; + } + } + + if (ret < 0) { + goto error; + } + + ret = sd_bus_message_exit_container(msg); + if (ret < 0) { + goto error; + } + + // PropertiesChanged arg 3: changed properties without values + sd_bus_message_enter_container(msg, 'a', "s"); + while ((ret = sd_bus_message_read_basic(msg, 's', &s)) > 0) { + if (strcmp(s, "CanGraphical") == 0) { + session->can_graphical = sd_seat_can_graphical(session->seat); + return 0; + } + } + + if (ret < 0) { + goto error; + } + + return 0; + +error: + return 0; +} + +static bool add_signal_matches(struct backend_logind *backend) { + static const char *logind = "org.freedesktop.login1"; + static const char *session_interface = "org.freedesktop.login1.Session"; + static const char *property_interface = "org.freedesktop.DBus.Properties"; + int ret; + + ret = sd_bus_match_signal(backend->bus, NULL, logind, backend->path, session_interface, + "PauseDevice", pause_device, backend); + if (ret < 0) { + return false; + } + + ret = sd_bus_match_signal(backend->bus, NULL, logind, backend->path, session_interface, + "ResumeDevice", resume_device, backend); + if (ret < 0) { + return false; + } + + ret = sd_bus_match_signal(backend->bus, NULL, logind, backend->path, property_interface, + "PropertiesChanged", session_properties_changed, backend); + if (ret < 0) { + return false; + } + + ret = sd_bus_match_signal(backend->bus, NULL, logind, backend->seat_path, property_interface, + "PropertiesChanged", seat_properties_changed, backend); + if (ret < 0) { + return false; + } + + return true; +} + +static bool contains_str(const char *needle, const char **haystack) { + for (int i = 0; haystack[i]; i++) { + if (strcmp(haystack[i], needle) == 0) { + return true; + } + } + + return false; +} + +static bool get_greeter_session(char **session_id) { + char *class = NULL; + char **user_sessions = NULL; + int user_session_count = sd_uid_get_sessions(getuid(), 1, &user_sessions); + + if (user_session_count < 0) { + goto out; + } + + if (user_session_count == 0) { + goto out; + } + + for (int i = 0; i < user_session_count; ++i) { + int ret = sd_session_get_class(user_sessions[i], &class); + if (ret < 0) { + continue; + } + + if (strcmp(class, "greeter") == 0) { + *session_id = strdup(user_sessions[i]); + goto out; + } + } + +out: + free(class); + for (int i = 0; i < user_session_count; ++i) { + free(user_sessions[i]); + } + free(user_sessions); + + return *session_id != NULL; +} + +static bool find_session_path(struct backend_logind *session) { + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", "GetSession", &error, &msg, "s", + session->id); + if (ret < 0) { + goto out; + } + + const char *path; + ret = sd_bus_message_read(msg, "o", &path); + if (ret < 0) { + goto out; + } + session->path = strdup(path); + +out: + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + + return ret >= 0; +} + +static bool find_seat_path(struct backend_logind *session) { + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", "GetSeat", &error, &msg, "s", + session->seat); + if (ret < 0) { + goto out; + } + + const char *path; + ret = sd_bus_message_read(msg, "o", &path); + if (ret < 0) { + goto out; + } + session->seat_path = strdup(path); + +out: + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + + return ret >= 0; +} + +static bool get_display_session(char **session_id) { + assert(session_id != NULL); + char *xdg_session_id = getenv("XDG_SESSION_ID"); + char *type = NULL; + char *state = NULL; + + if (xdg_session_id) { + // This just checks whether the supplied session ID is valid + if (sd_session_is_active(xdg_session_id) < 0) { + goto error; + } + *session_id = strdup(xdg_session_id); + return true; + } + + // If there's a session active for the current process then just use + // that + int ret = sd_pid_get_session(getpid(), session_id); + if (ret == 0) { + return true; + } + + // Find any active sessions for the user if the process isn't part of an + // active session itself + ret = sd_uid_get_display(getuid(), session_id); + if (ret < 0 && ret != -ENODATA) { + goto error; + } + + if (ret != 0 && !get_greeter_session(session_id)) { + goto error; + } + + assert(*session_id != NULL); + + // Check that the available session is graphical + ret = sd_session_get_type(*session_id, &type); + if (ret < 0) { + goto error; + } + + const char *graphical_session_types[] = {"wayland", "x11", "mir", NULL}; + if (!contains_str(type, graphical_session_types)) { + goto error; + } + + // Check that the session is active + ret = sd_session_get_state(*session_id, &state); + if (ret < 0) { + goto error; + } + + const char *active_states[] = {"active", "online", NULL}; + if (!contains_str(state, active_states)) { + goto error; + } + + free(type); + free(state); + return true; + +error: + free(type); + free(state); + free(*session_id); + *session_id = NULL; + + return false; +} + +static struct libseat *logind_open_seat(struct libseat_seat_listener *listener, void *data) { + struct backend_logind *backend = calloc(1, sizeof(struct backend_logind)); + if (backend == NULL) { + return NULL; + } + + if (!get_display_session(&backend->id)) { + goto error; + } + + int ret = sd_session_get_seat(backend->id, &backend->seat); + if (ret < 0) { + goto error; + } + + ret = sd_bus_default_system(&backend->bus); + if (ret < 0) { + goto error; + } + + if (!find_session_path(backend)) { + goto error; + } + + if (!find_seat_path(backend)) { + goto error; + } + + if (!add_signal_matches(backend)) { + goto error; + } + + if (!session_activate(backend)) { + goto error; + } + + if (!take_control(backend)) { + goto error; + } + + backend->can_graphical = sd_seat_can_graphical(backend->seat); + while (!backend->can_graphical) { + if (poll_connection(backend, -1) == -1) { + goto error; + } + } + + backend->initial_setup = true; + backend->active = true; + backend->seat_listener = listener; + backend->seat_listener_data = data; + backend->base.impl = &logind_impl; + + return &backend->base; + +error: + if (backend != NULL) { + destroy(backend); + } + return NULL; +} + +const struct libseat_impl logind_impl = { + .open_seat = logind_open_seat, + .disable_seat = disable_seat, + .close_seat = close_seat, + .seat_name = seat_name, + .open_device = open_device, + .close_device = close_device, + .switch_session = switch_session, + .get_fd = get_fd, + .dispatch = dispatch_background, +}; diff --git a/libseat/backend/seatd.c b/libseat/backend/seatd.c new file mode 100644 index 0000000..3b4a04b --- /dev/null +++ b/libseat/backend/seatd.c @@ -0,0 +1,545 @@ +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> + +#include "backend.h" +#include "connection.h" +#include "libseat.h" +#include "list.h" +#include "log.h" +#include "protocol.h" + +#ifdef BUILTIN_ENABLED +#include "poller.h" +#include "server.h" +#endif + +const struct libseat_impl seatd_impl; +const struct libseat_impl builtin_impl; + +struct pending_event { + int opcode; +}; + +struct backend_seatd { + struct libseat base; + struct connection connection; + struct libseat_seat_listener *seat_listener; + void *seat_listener_data; + struct list pending_events; + + char seat_name[MAX_SEAT_LEN]; +}; + +static int set_nonblock(int fd) { + int flags; + if ((flags = fcntl(fd, F_GETFD)) == -1 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { + return -1; + } + if ((flags = fcntl(fd, F_GETFL)) == -1 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { + return -1; + } + return 0; +} + +static int seatd_connect(void) { + union { + struct sockaddr_un unix; + struct sockaddr generic; + } addr = {0}; + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + return -1; + } + if (set_nonblock(fd) == -1) { + close(fd); + return -1; + } + char *path = getenv("SEATD_SOCK"); + if (path == NULL) { + path = "/run/seatd.sock"; + } + addr.unix.sun_family = AF_UNIX; + strncpy(addr.unix.sun_path, path, sizeof addr.unix.sun_path); + socklen_t size = offsetof(struct sockaddr_un, sun_path) + strlen(addr.unix.sun_path); + if (connect(fd, &addr.generic, size) == -1) { + close(fd); + return -1; + }; + return fd; +} + +static struct backend_seatd *backend_seatd_from_libseat_backend(struct libseat *base) { + assert(base); +#ifdef BUILTIN_ENABLED + assert(base->impl == &seatd_impl || base->impl == &builtin_impl); +#else + assert(base->impl == &seatd_impl); +#endif + return (struct backend_seatd *)base; +} + +static void handle_enable_seat(struct backend_seatd *backend) { + if (backend->seat_listener != NULL && backend->seat_listener->enable_seat != NULL) { + backend->seat_listener->enable_seat(&backend->base, backend->seat_listener_data); + } +} + +static void handle_disable_seat(struct backend_seatd *backend) { + if (backend->seat_listener != NULL && backend->seat_listener->disable_seat != NULL) { + backend->seat_listener->disable_seat(&backend->base, backend->seat_listener_data); + } +} + +static size_t read_header(struct connection *connection, uint16_t expected_opcode) { + struct proto_header header; + if (connection_get(connection, &header, sizeof header) == -1) { + return SIZE_MAX; + } + if (header.opcode != expected_opcode) { + connection_restore(connection, sizeof header); + errno = EBADMSG; + return SIZE_MAX; + } + + return header.size; +} + +static int queue_event(struct backend_seatd *backend, int opcode) { + struct pending_event *ev = calloc(1, sizeof(struct pending_event)); + if (ev == NULL) { + return -1; + } + + ev->opcode = opcode; + list_add(&backend->pending_events, ev); + return 0; +} + +static void execute_events(struct backend_seatd *backend) { + while (backend->pending_events.length > 0) { + struct pending_event *ev = list_pop_front(&backend->pending_events); + int opcode = ev->opcode; + free(ev); + + switch (opcode) { + case SERVER_DISABLE_SEAT: + handle_disable_seat(backend); + break; + case SERVER_ENABLE_SEAT: + handle_enable_seat(backend); + break; + default: + abort(); + } + } +} + +static int dispatch_pending(struct backend_seatd *backend, int *opcode) { + int packets = 0; + struct proto_header header; + while (connection_get(&backend->connection, &header, sizeof header) != -1) { + packets++; + switch (header.opcode) { + case SERVER_DISABLE_SEAT: + case SERVER_ENABLE_SEAT: + queue_event(backend, header.opcode); + break; + default: + if (opcode != NULL && + connection_pending(&backend->connection) >= header.size) { + *opcode = header.opcode; + } + connection_restore(&backend->connection, sizeof header); + return packets; + } + } + return packets; +} + +static int poll_connection(struct backend_seatd *backend, int timeout) { + struct pollfd fd = { + .fd = backend->connection.fd, + .events = POLLIN, + }; + + if (poll(&fd, 1, timeout) == -1) { + return (errno == EAGAIN || errno == EINTR) ? 0 : -1; + } + + if (fd.revents & (POLLERR | POLLHUP)) { + errno = EPIPE; + return -1; + } + + int len = 0; + if (fd.revents & POLLIN) { + len = connection_read(&backend->connection); + if (len == 0 || (len == -1 && errno != EAGAIN)) { + return -1; + } + } + + return len; +} + +static int dispatch(struct backend_seatd *backend) { + if (connection_flush(&backend->connection) == -1) { + return -1; + } + int opcode = 0; + while (dispatch_pending(backend, &opcode) == 0 && opcode == 0) { + if (poll_connection(backend, -1) == -1) { + return -1; + } + } + return 0; +} + +static void check_error(struct connection *connection) { + struct proto_header header; + if (connection_get(connection, &header, sizeof header) == -1) { + return; + } + if (header.opcode != SERVER_ERROR) { + errno = EBADMSG; + return; + } + + struct proto_server_error msg; + if (connection_get(connection, &msg, sizeof msg) == -1) { + return; + } + + errno = msg.error_code; +} + +static int get_fd(struct libseat *base) { + struct backend_seatd *backend = backend_seatd_from_libseat_backend(base); + return backend->connection.fd; +} + +static int dispatch_background(struct libseat *base, int timeout) { + struct backend_seatd *backend = backend_seatd_from_libseat_backend(base); + int dispatched = dispatch_pending(backend, NULL); + if (dispatched > 0) { + // We don't want to block if we dispatched something, as the + // caller might be waiting for the result. However, we'd also + // like to read anything pending. + timeout = 0; + } + int read = 0; + if (timeout == 0) { + read = connection_read(&backend->connection); + } else { + read = poll_connection(backend, timeout); + } + if (read > 0) { + dispatched += dispatch_pending(backend, NULL); + } else if (read == -1 && errno != EAGAIN) { + return -1; + } + + execute_events(backend); + return dispatched; +} + +static void destroy(struct backend_seatd *backend) { + if (backend->connection.fd != -1) { + close(backend->connection.fd); + backend->connection.fd = -1; + } + connection_close_fds(&backend->connection); + for (size_t idx = 0; idx < backend->pending_events.length; idx++) { + free(backend->pending_events.items[idx]); + } + list_free(&backend->pending_events); + free(backend); +} + +static struct libseat *_open_seat(struct libseat_seat_listener *listener, void *data, int fd) { + struct backend_seatd *backend = calloc(1, sizeof(struct backend_seatd)); + if (backend == NULL) { + close(fd); + return NULL; + } + backend->seat_listener = listener; + backend->seat_listener_data = data; + backend->connection.fd = fd; + backend->base.impl = &seatd_impl; + list_init(&backend->pending_events); + + struct proto_header header = { + .opcode = CLIENT_OPEN_SEAT, + .size = 0, + }; + + if (connection_put(&backend->connection, &header, sizeof header) == -1 || + dispatch(backend) == -1) { + destroy(backend); + return NULL; + } + + size_t size = read_header(&backend->connection, SERVER_SEAT_OPENED); + if (size == SIZE_MAX) { + check_error(&backend->connection); + destroy(backend); + return NULL; + } + + struct proto_server_seat_opened rmsg; + if (sizeof rmsg > size) { + errno = EBADMSG; + return NULL; + } + + if (connection_get(&backend->connection, &rmsg, sizeof rmsg) == -1) { + return NULL; + }; + + if (sizeof rmsg + rmsg.seat_name_len > size || + rmsg.seat_name_len >= sizeof backend->seat_name) { + errno = EBADMSG; + return NULL; + } + + if (connection_get(&backend->connection, backend->seat_name, rmsg.seat_name_len) == -1) { + return NULL; + }; + + return &backend->base; +} + +static struct libseat *open_seat(struct libseat_seat_listener *listener, void *data) { + int fd = seatd_connect(); + if (fd == -1) { + return NULL; + } + + return _open_seat(listener, data, fd); +} + +static int close_seat(struct libseat *base) { + struct backend_seatd *backend = backend_seatd_from_libseat_backend(base); + + struct proto_header header = { + .opcode = CLIENT_CLOSE_SEAT, + .size = 0, + }; + + if (connection_put(&backend->connection, &header, sizeof header) == -1 || + dispatch(backend) == -1) { + destroy(backend); + return -1; + } + + size_t size = read_header(&backend->connection, SERVER_SEAT_CLOSED); + if (size == SIZE_MAX) { + check_error(&backend->connection); + destroy(backend); + return -1; + } + + destroy(backend); + return 0; +} + +static const char *seat_name(struct libseat *base) { + struct backend_seatd *backend = backend_seatd_from_libseat_backend(base); + return backend->seat_name; +} + +static int open_device(struct libseat *base, const char *path, int *fd) { + struct backend_seatd *backend = backend_seatd_from_libseat_backend(base); + + size_t pathlen = strlen(path) + 1; + if (pathlen > MAX_PATH_LEN) { + errno = EINVAL; + return -1; + } + + struct proto_client_open_device msg = { + .path_len = (uint16_t)pathlen, + }; + struct proto_header header = { + .opcode = CLIENT_OPEN_DEVICE, + .size = sizeof msg + pathlen, + }; + + if (connection_put(&backend->connection, &header, sizeof header) == -1 || + connection_put(&backend->connection, &msg, sizeof msg) == -1 || + connection_put(&backend->connection, path, pathlen) == -1 || dispatch(backend) == -1) { + return -1; + } + + size_t size = read_header(&backend->connection, SERVER_DEVICE_OPENED); + if (size == SIZE_MAX) { + check_error(&backend->connection); + return -1; + } + + struct proto_server_device_opened rmsg; + if (sizeof rmsg > size) { + errno = EBADMSG; + return -1; + } + if (connection_get(&backend->connection, &rmsg, sizeof rmsg) == -1) { + return -1; + } + + int received_fd = connection_get_fd(&backend->connection); + if (received_fd == -1) { + return -1; + } + + *fd = received_fd; + return rmsg.device_id; +} + +static int close_device(struct libseat *base, int device_id) { + struct backend_seatd *backend = backend_seatd_from_libseat_backend(base); + if (device_id < 0) { + errno = EINVAL; + return -1; + } + + struct proto_client_close_device msg = { + .device_id = device_id, + }; + struct proto_header header = { + .opcode = CLIENT_CLOSE_DEVICE, + .size = sizeof msg, + }; + + if (connection_put(&backend->connection, &header, sizeof header) == -1 || + connection_put(&backend->connection, &msg, sizeof msg) == -1 || dispatch(backend) == -1) { + return -1; + } + + size_t size = read_header(&backend->connection, SERVER_DEVICE_CLOSED); + if (size == SIZE_MAX) { + check_error(&backend->connection); + return -1; + } + + struct proto_server_device_closed rmsg; + if (sizeof rmsg > size) { + errno = EBADMSG; + return -1; + } + if (connection_get(&backend->connection, &rmsg, sizeof rmsg) == -1) { + return -1; + } + if (rmsg.device_id != device_id) { + errno = EBADMSG; + return -1; + } + + return 0; +} + +static int switch_session(struct libseat *base, int session) { + struct backend_seatd *backend = backend_seatd_from_libseat_backend(base); + if (session < 0) { + return -1; + } + + struct proto_client_switch_session msg = { + .session = session, + }; + struct proto_header header = { + .opcode = CLIENT_SWITCH_SESSION, + .size = sizeof msg, + }; + + if (connection_put(&backend->connection, &header, sizeof header) == -1 || + connection_put(&backend->connection, &msg, sizeof msg) == -1 || + connection_flush(&backend->connection) == -1) { + return -1; + } + + return 0; +} + +static int disable_seat(struct libseat *base) { + struct backend_seatd *backend = backend_seatd_from_libseat_backend(base); + struct proto_header header = { + .opcode = CLIENT_DISABLE_SEAT, + .size = 0, + }; + + if (connection_put(&backend->connection, &header, sizeof header) == -1 || + connection_flush(&backend->connection) == -1) { + return -1; + } + + return 0; +} + +const struct libseat_impl seatd_impl = { + .open_seat = open_seat, + .disable_seat = disable_seat, + .close_seat = close_seat, + .seat_name = seat_name, + .open_device = open_device, + .close_device = close_device, + .switch_session = switch_session, + .get_fd = get_fd, + .dispatch = dispatch_background, +}; + +#ifdef BUILTIN_ENABLED +static struct libseat *builtin_open_seat(struct libseat_seat_listener *listener, void *data) { + int fds[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1) { + return NULL; + } + + pid_t pid = fork(); + if (pid == -1) { + close(fds[0]); + close(fds[1]); + return NULL; + } else if (pid == 0) { + int fd = fds[0]; + struct server *server = server_create(); + if (server == NULL) { + close(fd); + exit(1); + } + if (server_add_client(server, fd) == -1) { + exit(1); + } + while (server->running) { + if (poller_poll(server->poller) == -1) { + exit(1); + } + } + close(fd); + exit(0); + } else { + int fd = fds[1]; + return _open_seat(listener, data, fd); + } +} + +const struct libseat_impl builtin_impl = { + .open_seat = builtin_open_seat, + .disable_seat = disable_seat, + .close_seat = close_seat, + .seat_name = seat_name, + .open_device = open_device, + .close_device = close_device, + .switch_session = switch_session, + .get_fd = get_fd, + .dispatch = dispatch_background, +}; +#endif diff --git a/libseat/libseat.c b/libseat/libseat.c new file mode 100644 index 0000000..a1aed18 --- /dev/null +++ b/libseat/libseat.c @@ -0,0 +1,111 @@ +#include <assert.h> +#include <errno.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "backend.h" +#include "compiler.h" +#include "libseat.h" +#include "log.h" + +extern const struct libseat_impl seatd_impl; +extern const struct libseat_impl logind_impl; +extern const struct libseat_impl builtin_impl; + +static const struct named_backend impls[] = { +#ifdef SEATD_ENABLED + {"seatd", &seatd_impl}, +#endif +#ifdef LOGIND_ENABLED + {"logind", &logind_impl}, +#endif +#ifdef BUILTIN_ENABLED + {"builtin", &builtin_impl}, +#endif + {NULL, NULL}, +}; + +#if !defined(SEATD_ENABLED) && !defined(LOGIND_ENABLED) && !defined(BUILTIN_ENABLED) +#error At least one backend must be enabled +#endif + +LIBSEAT_EXPORT struct libseat *libseat_open_seat(struct libseat_seat_listener *listener, void *data) { + if (listener == NULL) { + errno = EINVAL; + return NULL; + } + + char *loglevel = getenv("SEATD_LOGLEVEL"); + enum libseat_log_level level = LIBSEAT_SILENT; + if (loglevel != NULL) { + if (strcmp(loglevel, "silent") == 0) { + level = LIBSEAT_SILENT; + } else if (strcmp(loglevel, "info") == 0) { + level = LIBSEAT_INFO; + } else if (strcmp(loglevel, "debug") == 0) { + level = LIBSEAT_DEBUG; + } + } + libseat_log_init(level); + + char *backend_type = getenv("LIBSEAT_BACKEND"); + struct libseat *backend = NULL; + for (const struct named_backend *iter = impls; iter->backend != NULL; iter++) { + log_debugf("libseat_open_seat: trying backend '%s'", iter->name); + if (backend_type != NULL && strcmp(backend_type, iter->name) != 0) { + continue; + } + backend = iter->backend->open_seat(listener, data); + if (backend != NULL) { + log_infof("libseat_open_seat: seat opened with backend '%s'", iter->name); + break; + } + } + if (backend == NULL) { + errno = ENOSYS; + } + return backend; +} + +LIBSEAT_EXPORT int libseat_disable_seat(struct libseat *seat) { + assert(seat && seat->impl); + return seat->impl->disable_seat(seat); +} + +LIBSEAT_EXPORT int libseat_close_seat(struct libseat *seat) { + assert(seat && seat->impl); + return seat->impl->close_seat(seat); +} + +LIBSEAT_EXPORT const char *libseat_seat_name(struct libseat *seat) { + assert(seat && seat->impl); + return seat->impl->seat_name(seat); +} + +LIBSEAT_EXPORT int libseat_open_device(struct libseat *seat, const char *path, int *fd) { + assert(seat && seat->impl); + return seat->impl->open_device(seat, path, fd); +} + +LIBSEAT_EXPORT int libseat_close_device(struct libseat *seat, int device_id) { + assert(seat && seat->impl); + return seat->impl->close_device(seat, device_id); +} + +LIBSEAT_EXPORT int libseat_get_fd(struct libseat *seat) { + assert(seat && seat->impl); + return seat->impl->get_fd(seat); +} + +LIBSEAT_EXPORT int libseat_dispatch(struct libseat *seat, int timeout) { + assert(seat && seat->impl); + return seat->impl->dispatch(seat, timeout); +} + +LIBSEAT_EXPORT int libseat_switch_session(struct libseat *seat, int session) { + assert(seat && seat->impl); + return seat->impl->switch_session(seat, session); +} |