diff options
Diffstat (limited to 'backend/session')
-rw-r--r-- | backend/session/direct-freebsd.c | 268 | ||||
-rw-r--r-- | backend/session/direct-ipc.c | 269 | ||||
-rw-r--r-- | backend/session/direct.c | 278 | ||||
-rw-r--r-- | backend/session/logind.c | 562 | ||||
-rw-r--r-- | backend/session/session.c | 359 |
5 files changed, 1736 insertions, 0 deletions
diff --git a/backend/session/direct-freebsd.c b/backend/session/direct-freebsd.c new file mode 100644 index 00000000..342d0d4e --- /dev/null +++ b/backend/session/direct-freebsd.c @@ -0,0 +1,268 @@ +#include <assert.h> +#include <dev/evdev/input.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/consio.h> +#include <sys/ioctl.h> +#include <sys/kbio.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <termios.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/session/interface.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include "backend/session/direct-ipc.h" +#include "util/signal.h" + +const struct session_impl session_direct; + +struct direct_session { + struct wlr_session base; + int tty_fd; + int old_tty; + int old_kbmode; + int sock; + pid_t child; + + struct wl_event_source *vt_source; +}; + +static struct direct_session *direct_session_from_session( + struct wlr_session *base) { + assert(base->impl == &session_direct); + return (struct direct_session *)base; +} + +static int direct_session_open(struct wlr_session *base, const char *path) { + struct direct_session *session = direct_session_from_session(base); + + int fd = direct_ipc_open(session->sock, path); + if (fd < 0) { + wlr_log(WLR_ERROR, "Failed to open %s: %s%s", path, strerror(-fd), + fd == -EINVAL ? "; is another display server running?" : ""); + return fd; + } + + return fd; +} + +static void direct_session_close(struct wlr_session *base, int fd) { + struct direct_session *session = direct_session_from_session(base); + + int ev; + struct drm_version dv = {0}; + if (ioctl(fd, DRM_IOCTL_VERSION, &dv) == 0) { + direct_ipc_dropmaster(session->sock, fd); + } else if (ioctl(fd, EVIOCGVERSION, &ev) == 0) { + ioctl(fd, EVIOCREVOKE, 0); + } + + close(fd); +} + +static bool direct_change_vt(struct wlr_session *base, unsigned vt) { + struct direct_session *session = direct_session_from_session(base); + + // Only seat0 has VTs associated with it + if (strcmp(session->base.seat, "seat0") != 0) { + return true; + } + + return ioctl(session->tty_fd, VT_ACTIVATE, (int)vt) == 0; +} + +static void direct_session_destroy(struct wlr_session *base) { + struct direct_session *session = direct_session_from_session(base); + + if (strcmp(session->base.seat, "seat0") == 0) { + struct vt_mode mode = { + .mode = VT_AUTO, + }; + + errno = 0; + + ioctl(session->tty_fd, KDSKBMODE, session->old_kbmode); + ioctl(session->tty_fd, KDSETMODE, KD_TEXT); + ioctl(session->tty_fd, VT_SETMODE, &mode); + + ioctl(session->tty_fd, VT_ACTIVATE, session->old_tty); + + if (errno) { + wlr_log(WLR_ERROR, "Failed to restore tty"); + } + + wl_event_source_remove(session->vt_source); + close(session->tty_fd); + } + + direct_ipc_finish(session->sock, session->child); + close(session->sock); + + free(session); +} + +static int vt_handler(int signo, void *data) { + struct direct_session *session = data; + struct drm_version dv = {0}; + struct wlr_device *dev; + + if (session->base.active) { + session->base.active = false; + wlr_signal_emit_safe(&session->base.session_signal, session); + + wl_list_for_each(dev, &session->base.devices, link) { + if (ioctl(dev->fd, DRM_IOCTL_VERSION, &dv) == 0) { + direct_ipc_dropmaster(session->sock, dev->fd); + } + } + + ioctl(session->tty_fd, VT_RELDISP, 1); + } else { + ioctl(session->tty_fd, VT_RELDISP, VT_ACKACQ); + + wl_list_for_each(dev, &session->base.devices, link) { + if (ioctl(dev->fd, DRM_IOCTL_VERSION, &dv) == 0) { + direct_ipc_setmaster(session->sock, dev->fd); + } + } + + session->base.active = true; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + + return 1; +} + +static bool setup_tty(struct direct_session *session, struct wl_display *display) { + int fd = -1, tty = -1, tty0_fd = -1, old_tty = 1; + if ((tty0_fd = open("/dev/ttyv0", O_RDWR | O_CLOEXEC)) < 0) { + wlr_log_errno(WLR_ERROR, "Could not open /dev/ttyv0 to find a free vt"); + goto error; + } + if (ioctl(tty0_fd, VT_GETACTIVE, &old_tty) != 0) { + wlr_log_errno(WLR_ERROR, "Could not get active vt"); + goto error; + } + if (ioctl(tty0_fd, VT_OPENQRY, &tty) != 0) { + wlr_log_errno(WLR_ERROR, "Could not find a free vt"); + goto error; + } + close(tty0_fd); + char tty_path[64]; + snprintf(tty_path, sizeof(tty_path), "/dev/ttyv%d", tty - 1); + wlr_log(WLR_INFO, "Using tty %s", tty_path); + fd = open(tty_path, O_RDWR | O_NOCTTY | O_CLOEXEC); + + if (fd == -1) { + wlr_log_errno(WLR_ERROR, "Cannot open tty"); + return false; + } + + ioctl(fd, VT_ACTIVATE, tty); + ioctl(fd, VT_WAITACTIVE, tty); + + int old_kbmode; + if (ioctl(fd, KDGKBMODE, &old_kbmode)) { + wlr_log_errno(WLR_ERROR, "Failed to read tty %d keyboard mode", tty); + goto error; + } + + if (ioctl(fd, KDSKBMODE, K_CODE)) { + wlr_log_errno(WLR_ERROR, + "Failed to set keyboard mode K_CODE on tty %d", tty); + goto error; + } + + if (ioctl(fd, KDSETMODE, KD_GRAPHICS)) { + wlr_log_errno(WLR_ERROR, "Failed to set graphics mode on tty %d", tty); + goto error; + } + + struct vt_mode mode = { + .mode = VT_PROCESS, + .relsig = SIGUSR2, + .acqsig = SIGUSR2, + .frsig = SIGIO, // has to be set + }; + + if (ioctl(fd, VT_SETMODE, &mode) < 0) { + wlr_log(WLR_ERROR, "Failed to take control of tty %d", tty); + goto error; + } + + struct wl_event_loop *loop = wl_display_get_event_loop(display); + session->vt_source = wl_event_loop_add_signal(loop, SIGUSR2, + vt_handler, session); + if (!session->vt_source) { + goto error; + } + + session->base.vtnr = tty; + session->tty_fd = fd; + session->old_tty = old_tty; + session->old_kbmode = old_kbmode; + + return true; + +error: + // In case we could not get the last active one, drop back to tty 1, + // better than hanging in a useless blank console. Otherwise activate the + // last active. + ioctl(fd, VT_ACTIVATE, old_tty); + close(fd); + return false; +} + +static struct wlr_session *direct_session_create(struct wl_display *disp) { + struct direct_session *session = calloc(1, sizeof(*session)); + if (!session) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + session->sock = direct_ipc_init(&session->child); + if (session->sock == -1) { + goto error_session; + } + + const char *seat = getenv("XDG_SEAT"); + if (!seat) { + seat = "seat0"; + } + + if (strcmp(seat, "seat0") == 0) { + if (!setup_tty(session, disp)) { + goto error_ipc; + } + } else { + session->base.vtnr = 0; + session->tty_fd = -1; + } + + wlr_log(WLR_INFO, "Successfully loaded direct session"); + + snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat); + session->base.impl = &session_direct; + return &session->base; + +error_ipc: + direct_ipc_finish(session->sock, session->child); + close(session->sock); +error_session: + free(session); + return NULL; +} + +const struct session_impl session_direct = { + .create = direct_session_create, + .destroy = direct_session_destroy, + .open = direct_session_open, + .close = direct_session_close, + .change_vt = direct_change_vt, +}; diff --git a/backend/session/direct-ipc.c b/backend/session/direct-ipc.c new file mode 100644 index 00000000..2b9634da --- /dev/null +++ b/backend/session/direct-ipc.c @@ -0,0 +1,269 @@ +#define _POSIX_C_SOURCE 200809L +#ifdef __FreeBSD__ +#define __BSD_VISIBLE 1 +#include <dev/evdev/input.h> +#endif +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <wlr/config.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#ifdef __linux__ +#include <sys/sysmacros.h> +#include <linux/major.h> +#endif +#include "backend/session/direct-ipc.h" + +enum { DRM_MAJOR = 226 }; + +#if WLR_HAS_LIBCAP +#include <sys/capability.h> + +static bool have_permissions(void) { + cap_t cap = cap_get_proc(); + cap_flag_value_t val; + + if (!cap || cap_get_flag(cap, CAP_SYS_ADMIN, CAP_PERMITTED, &val) || val != CAP_SET) { + wlr_log(WLR_ERROR, "Do not have CAP_SYS_ADMIN; cannot become DRM master"); + cap_free(cap); + return false; + } + + cap_free(cap); + return true; +} +#else +static bool have_permissions(void) { +#ifdef __linux__ + if (geteuid() != 0) { + wlr_log(WLR_ERROR, "Do not have root privileges; cannot become DRM master"); + return false; + } +#endif + return true; +} +#endif + +static void send_msg(int sock, int fd, void *buf, size_t buf_len) { + char control[CMSG_SPACE(sizeof(fd))] = {0}; + struct iovec iovec = { .iov_base = buf, .iov_len = buf_len }; + struct msghdr msghdr = {0}; + + if (buf) { + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + } + + if (fd >= 0) { + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msghdr); + *cmsg = (struct cmsghdr) { + .cmsg_level = SOL_SOCKET, + .cmsg_type = SCM_RIGHTS, + .cmsg_len = CMSG_LEN(sizeof(fd)), + }; + memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd)); + } + + ssize_t ret; + do { + ret = sendmsg(sock, &msghdr, 0); + } while (ret < 0 && errno == EINTR); +} + +static ssize_t recv_msg(int sock, int *fd_out, void *buf, size_t buf_len) { + char control[CMSG_SPACE(sizeof(*fd_out))] = {0}; + struct iovec iovec = { .iov_base = buf, .iov_len = buf_len }; + struct msghdr msghdr = {0}; + + if (buf) { + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + } + + if (fd_out) { + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + } + + ssize_t ret; + do { + ret = recvmsg(sock, &msghdr, MSG_CMSG_CLOEXEC); + } while (ret < 0 && errno == EINTR); + + if (fd_out) { + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msghdr); + if (cmsg) { + memcpy(fd_out, CMSG_DATA(cmsg), sizeof(*fd_out)); + } else { + *fd_out = -1; + } + } + + return ret; +} + +enum msg_type { + MSG_OPEN, + MSG_SETMASTER, + MSG_DROPMASTER, + MSG_END, +}; + +struct msg { + enum msg_type type; + char path[256]; +}; + +static void communicate(int sock) { + struct msg msg; + int drm_fd = -1; + bool running = true; + + while (running && recv_msg(sock, &drm_fd, &msg, sizeof(msg)) > 0) { + switch (msg.type) { + case MSG_OPEN: + errno = 0; + + // These are the same flags that logind opens files with + int fd = open(msg.path, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + int ret = errno; + if (fd == -1) { + goto error; + } + +#ifndef __FreeBSD__ + struct stat st; + if (fstat(fd, &st) < 0) { + ret = errno; + goto error; + } + + uint32_t maj = major(st.st_rdev); + if (maj != INPUT_MAJOR && maj != DRM_MAJOR) { + ret = ENOTSUP; + goto error; + } + + if (maj == DRM_MAJOR && drmSetMaster(fd)) { + ret = errno; + } +#else + int ev; + struct drm_version dv = {0}; + if (ioctl(fd, EVIOCGVERSION, &ev) == -1 && + ioctl(fd, DRM_IOCTL_VERSION, &dv) == -1) { + ret = ENOTSUP; + goto error; + } + + if (dv.version_major != 0 && drmSetMaster(fd)) { + ret = errno; + } +#endif + +error: + send_msg(sock, ret ? -1 : fd, &ret, sizeof(ret)); + if (fd >= 0) { + close(fd); + } + + break; + + case MSG_SETMASTER: + drmSetMaster(drm_fd); + close(drm_fd); + send_msg(sock, -1, NULL, 0); + break; + + case MSG_DROPMASTER: + drmDropMaster(drm_fd); + close(drm_fd); + send_msg(sock, -1, NULL, 0); + break; + + case MSG_END: + running = false; + send_msg(sock, -1, NULL, 0); + break; + } + } + + close(sock); +} + +int direct_ipc_open(int sock, const char *path) { + struct msg msg = { .type = MSG_OPEN }; + snprintf(msg.path, sizeof(msg.path), "%s", path); + + send_msg(sock, -1, &msg, sizeof(msg)); + + int fd, err, ret; + int retry = 0; + do { + ret = recv_msg(sock, &fd, &err, sizeof(err)); + } while (ret == 0 && retry++ < 3); + + return err ? -err : fd; +} + +void direct_ipc_setmaster(int sock, int fd) { + struct msg msg = { .type = MSG_SETMASTER }; + + send_msg(sock, fd, &msg, sizeof(msg)); + recv_msg(sock, NULL, NULL, 0); +} + +void direct_ipc_dropmaster(int sock, int fd) { + struct msg msg = { .type = MSG_DROPMASTER }; + + send_msg(sock, fd, &msg, sizeof(msg)); + recv_msg(sock, NULL, NULL, 0); +} + +void direct_ipc_finish(int sock, pid_t pid) { + struct msg msg = { .type = MSG_END }; + + send_msg(sock, -1, &msg, sizeof(msg)); + recv_msg(sock, NULL, NULL, 0); + + waitpid(pid, NULL, 0); +} + +int direct_ipc_init(pid_t *pid_out) { + if (!have_permissions()) { + return -1; + } + + int sock[2]; + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sock) < 0) { + wlr_log_errno(WLR_ERROR, "Failed to create socket pair"); + return -1; + } + + pid_t pid = fork(); + if (pid < 0) { + wlr_log_errno(WLR_ERROR, "Fork failed"); + close(sock[0]); + close(sock[1]); + return -1; + } else if (pid == 0) { + close(sock[0]); + communicate(sock[1]); + _Exit(0); + } + + close(sock[1]); + *pid_out = pid; + return sock[0]; +} diff --git a/backend/session/direct.c b/backend/session/direct.c new file mode 100644 index 00000000..0912cd58 --- /dev/null +++ b/backend/session/direct.c @@ -0,0 +1,278 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/input.h> +#include <linux/kd.h> +#include <linux/major.h> +#include <linux/vt.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/session/interface.h> +#include <wlr/util/log.h> +#include "backend/session/direct-ipc.h" +#include "util/signal.h" + +enum { DRM_MAJOR = 226 }; + +const struct session_impl session_direct; + +struct direct_session { + struct wlr_session base; + int tty_fd; + int old_kbmode; + int sock; + pid_t child; + + struct wl_event_source *vt_source; +}; + +static struct direct_session *direct_session_from_session( + struct wlr_session *base) { + assert(base->impl == &session_direct); + return (struct direct_session *)base; +} + +static int direct_session_open(struct wlr_session *base, const char *path) { + struct direct_session *session = direct_session_from_session(base); + + int fd = direct_ipc_open(session->sock, path); + if (fd < 0) { + wlr_log(WLR_ERROR, "Failed to open %s: %s%s", path, strerror(-fd), + fd == -EINVAL ? "; is another display server running?" : ""); + return fd; + } + + struct stat st; + if (fstat(fd, &st) < 0) { + close(fd); + return -errno; + } + + if (major(st.st_rdev) == DRM_MAJOR) { + direct_ipc_setmaster(session->sock, fd); + } + + return fd; +} + +static void direct_session_close(struct wlr_session *base, int fd) { + struct direct_session *session = direct_session_from_session(base); + + struct stat st; + if (fstat(fd, &st) < 0) { + wlr_log_errno(WLR_ERROR, "Stat failed"); + close(fd); + return; + } + + if (major(st.st_rdev) == DRM_MAJOR) { + direct_ipc_dropmaster(session->sock, fd); + } else if (major(st.st_rdev) == INPUT_MAJOR) { + ioctl(fd, EVIOCREVOKE, 0); + } + + close(fd); +} + +static bool direct_change_vt(struct wlr_session *base, unsigned vt) { + struct direct_session *session = direct_session_from_session(base); + + // Only seat0 has VTs associated with it + if (strcmp(session->base.seat, "seat0") != 0) { + return true; + } + + return ioctl(session->tty_fd, VT_ACTIVATE, (int)vt) == 0; +} + +static void direct_session_destroy(struct wlr_session *base) { + struct direct_session *session = direct_session_from_session(base); + + if (strcmp(session->base.seat, "seat0") == 0) { + struct vt_mode mode = { + .mode = VT_AUTO, + }; + errno = 0; + + ioctl(session->tty_fd, KDSKBMODE, session->old_kbmode); + ioctl(session->tty_fd, KDSETMODE, KD_TEXT); + ioctl(session->tty_fd, VT_SETMODE, &mode); + + if (errno) { + wlr_log(WLR_ERROR, "Failed to restore tty"); + } + + wl_event_source_remove(session->vt_source); + close(session->tty_fd); + } + + direct_ipc_finish(session->sock, session->child); + close(session->sock); + + free(session); +} + +static int vt_handler(int signo, void *data) { + struct direct_session *session = data; + + if (session->base.active) { + session->base.active = false; + wlr_signal_emit_safe(&session->base.session_signal, session); + + struct wlr_device *dev; + wl_list_for_each(dev, &session->base.devices, link) { + if (major(dev->dev) == DRM_MAJOR) { + direct_ipc_dropmaster(session->sock, + dev->fd); + } + } + + ioctl(session->tty_fd, VT_RELDISP, 1); + } else { + ioctl(session->tty_fd, VT_RELDISP, VT_ACKACQ); + + struct wlr_device *dev; + wl_list_for_each(dev, &session->base.devices, link) { + if (major(dev->dev) == DRM_MAJOR) { + direct_ipc_setmaster(session->sock, + dev->fd); + } + } + + session->base.active = true; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + + return 1; +} + +static bool setup_tty(struct direct_session *session, struct wl_display *display) { + int fd = open("/dev/tty", O_RDWR); + if (fd == -1) { + wlr_log_errno(WLR_ERROR, "Cannot open /dev/tty"); + return false; + } + + struct vt_stat vt_stat; + if (ioctl(fd, VT_GETSTATE, &vt_stat)) { + wlr_log_errno(WLR_ERROR, "Could not get current tty number"); + goto error; + } + + int tty = vt_stat.v_active; + int ret, kd_mode, old_kbmode; + + ret = ioctl(fd, KDGETMODE, &kd_mode); + if (ret) { + wlr_log_errno(WLR_ERROR, "Failed to get tty mode"); + goto error; + } + + if (kd_mode != KD_TEXT) { + wlr_log(WLR_ERROR, + "tty already in graphics mode; is another display server running?"); + goto error; + } + + ioctl(fd, VT_ACTIVATE, tty); + ioctl(fd, VT_WAITACTIVE, tty); + + if (ioctl(fd, KDGKBMODE, &old_kbmode)) { + wlr_log_errno(WLR_ERROR, "Failed to read keyboard mode"); + goto error; + } + + if (ioctl(fd, KDSKBMODE, K_OFF)) { + wlr_log_errno(WLR_ERROR, "Failed to set keyboard mode"); + goto error; + } + + if (ioctl(fd, KDSETMODE, KD_GRAPHICS)) { + wlr_log_errno(WLR_ERROR, "Failed to set graphics mode on tty"); + goto error; + } + + struct vt_mode mode = { + .mode = VT_PROCESS, + .relsig = SIGUSR2, + .acqsig = SIGUSR2, + }; + + if (ioctl(fd, VT_SETMODE, &mode) < 0) { + wlr_log(WLR_ERROR, "Failed to take control of tty"); + goto error; + } + + struct wl_event_loop *loop = wl_display_get_event_loop(display); + session->vt_source = wl_event_loop_add_signal(loop, SIGUSR2, + vt_handler, session); + if (!session->vt_source) { + goto error; + } + + session->base.vtnr = tty; + session->tty_fd = fd; + session->old_kbmode = old_kbmode; + + return true; + +error: + close(fd); + return false; +} + +static struct wlr_session *direct_session_create(struct wl_display *disp) { + struct direct_session *session = calloc(1, sizeof(*session)); + if (!session) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + session->sock = direct_ipc_init(&session->child); + if (session->sock == -1) { + goto error_session; + } + + const char *seat = getenv("XDG_SEAT"); + if (!seat) { + seat = "seat0"; + } + + if (strcmp(seat, "seat0") == 0) { + if (!setup_tty(session, disp)) { + goto error_ipc; + } + } else { + session->base.vtnr = 0; + session->tty_fd = -1; + } + + snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat); + session->base.impl = &session_direct; + + wlr_log(WLR_INFO, "Successfully loaded direct session"); + return &session->base; + +error_ipc: + direct_ipc_finish(session->sock, session->child); + close(session->sock); +error_session: + free(session); + return NULL; +} + +const struct session_impl session_direct = { + .create = direct_session_create, + .destroy = direct_session_destroy, + .open = direct_session_open, + .close = direct_session_close, + .change_vt = direct_change_vt, +}; diff --git a/backend/session/logind.c b/backend/session/logind.c new file mode 100644 index 00000000..9a1383ce --- /dev/null +++ b/backend/session/logind.c @@ -0,0 +1,562 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/session/interface.h> +#include <wlr/config.h> +#include <wlr/util/log.h> +#include "util/signal.h" + +#if WLR_HAS_SYSTEMD + #include <systemd/sd-bus.h> + #include <systemd/sd-login.h> +#elif WLR_HAS_ELOGIND + #include <elogind/sd-bus.h> + #include <elogind/sd-login.h> +#endif + +enum { DRM_MAJOR = 226 }; + +const struct session_impl session_logind; + +struct logind_session { + struct wlr_session base; + + sd_bus *bus; + struct wl_event_source *event; + + char *id; + char *path; + + // specifies whether a drm device was taken + // if so, the session will be (de)activated with the drm fd, + // otherwise with the dbus PropertiesChanged on "active" signal + bool has_drm; +}; + +static struct logind_session *logind_session_from_session( + struct wlr_session *base) { + assert(base->impl == &session_logind); + return (struct logind_session *)base; +} + +static int logind_take_device(struct wlr_session *base, const char *path) { + struct logind_session *session = logind_session_from_session(base); + + int fd = -1; + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + struct stat st; + if (stat(path, &st) < 0) { + wlr_log(WLR_ERROR, "Failed to stat '%s'", path); + return -1; + } + + if (major(st.st_rdev) == DRM_MAJOR) { + session->has_drm = true; + } + + 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) { + wlr_log(WLR_ERROR, "Failed to take device '%s': %s", path, + error.message); + goto out; + } + + int paused = 0; + ret = sd_bus_message_read(msg, "hb", &fd, &paused); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse D-Bus response for '%s': %s", + path, strerror(-ret)); + goto out; + } + + // The original fd seems to be closed when the message is freed + // so we just clone it. + fd = fcntl(fd, F_DUPFD_CLOEXEC, 0); + if (fd < 0) { + wlr_log(WLR_ERROR, "Failed to clone file descriptor for '%s': %s", + path, strerror(errno)); + goto out; + } + +out: + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return fd; +} + +static void logind_release_device(struct wlr_session *base, int fd) { + struct logind_session *session = logind_session_from_session(base); + + struct stat st; + if (fstat(fd, &st) < 0) { + wlr_log(WLR_ERROR, "Failed to stat device '%d': %s", fd, + strerror(errno)); + return; + } + + 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", "ReleaseDevice", + &error, &msg, "uu", major(st.st_rdev), minor(st.st_rdev)); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to release device '%d': %s", fd, + error.message); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + close(fd); +} + +static bool logind_change_vt(struct wlr_session *base, unsigned vt) { + struct logind_session *session = logind_session_from_session(base); + + // Only seat0 has VTs associated with it + if (strcmp(session->base.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/self", "org.freedesktop.login1.Seat", "SwitchTo", + &error, &msg, "u", (uint32_t)vt); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to change to vt '%d'", vt); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return ret >= 0; +} + +static bool find_session_path(struct logind_session *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) { + wlr_log(WLR_ERROR, "Failed to get session path: %s", error.message); + goto out; + } + + const char *path; + ret = sd_bus_message_read(msg, "o", &path); + if (ret < 0) { + wlr_log(WLR_ERROR, "Could not parse session path: %s", error.message); + goto out; + } + session->path = strdup(path); + +out: + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + + return ret >= 0; +} + +static bool session_activate(struct logind_session *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", + session->path, "org.freedesktop.login1.Session", "Activate", + &error, &msg, ""); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to activate session: %s", error.message); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return ret >= 0; +} + +static bool take_control(struct logind_session *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", + session->path, "org.freedesktop.login1.Session", "TakeControl", + &error, &msg, "b", false); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to take control of session: %s", + error.message); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return ret >= 0; +} + +static void release_control(struct logind_session *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", + session->path, "org.freedesktop.login1.Session", "ReleaseControl", + &error, &msg, ""); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to release control of session: %s", + error.message); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); +} + +static void logind_session_destroy(struct wlr_session *base) { + struct logind_session *session = logind_session_from_session(base); + + release_control(session); + + wl_event_source_remove(session->event); + sd_bus_unref(session->bus); + free(session->id); + free(session->path); + free(session); +} + +static int session_removed(sd_bus_message *msg, void *userdata, + sd_bus_error *ret_error) { + wlr_log(WLR_INFO, "SessionRemoved signal received"); + return 0; +} + +static struct wlr_device *find_device(struct wlr_session *session, + dev_t devnum) { + struct wlr_device *dev; + + wl_list_for_each(dev, &session->devices, link) { + if (dev->dev == devnum) { + return dev; + } + } + + wlr_log(WLR_ERROR, "Tried to use dev_t %lu not opened by session", + (unsigned long)devnum); + assert(0); +} + +static int pause_device(sd_bus_message *msg, void *userdata, + sd_bus_error *ret_error) { + struct logind_session *session = userdata; + int ret; + + uint32_t major, minor; + const char *type; + ret = sd_bus_message_read(msg, "uus", &major, &minor, &type); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse D-Bus response for PauseDevice: %s", + strerror(-ret)); + goto error; + } + + if (major == DRM_MAJOR) { + assert(session->has_drm); + session->base.active = false; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + + if (strcmp(type, "pause") == 0) { + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "PauseDeviceComplete", + ret_error, &msg, "uu", major, minor); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to send PauseDeviceComplete signal: %s", + strerror(-ret)); + } + } + +error: + return 0; +} + +static int resume_device(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { + struct logind_session *session = userdata; + int ret; + + int fd; + uint32_t major, minor; + ret = sd_bus_message_read(msg, "uuh", &major, &minor, &fd); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse D-Bus response for ResumeDevice: %s", + strerror(-ret)); + goto error; + } + + if (major == DRM_MAJOR) { + struct wlr_device *dev = find_device(&session->base, makedev(major, minor)); + dup2(fd, dev->fd); + + if (!session->base.active) { + session->base.active = true; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + } + +error: + return 0; +} + +static int properties_changed(sd_bus_message *msg, void *userdata, + sd_bus_error *ret_error) { + struct logind_session *session = userdata; + int ret = 0; + + // if we have a drm fd we don't depend on this + if (session->has_drm) { + 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 + wlr_log(WLR_DEBUG, "ignoring PropertiesChanged from %s", interface); + 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; + } + + if (session->base.active != active) { + session->base.active = active; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + 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) { + wlr_log(WLR_ERROR, "Failed to get 'Active' property: %s", + error.message); + return 0; + } + + if (session->base.active != active) { + session->base.active = active; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + return 0; + } + } + + if (ret < 0) { + goto error; + } + + return 0; + +error: + wlr_log(WLR_ERROR, "Failed to parse D-Bus PropertiesChanged: %s", + strerror(-ret)); + return 0; +} + +static bool add_signal_matches(struct logind_session *session) { + int ret; + + char str[256]; + const char *fmt = "type='signal'," + "sender='org.freedesktop.login1'," + "interface='org.freedesktop.login1.%s'," + "member='%s'," + "path='%s'"; + + snprintf(str, sizeof(str), fmt, "Manager", "SessionRemoved", + "/org/freedesktop/login1"); + ret = sd_bus_add_match(session->bus, NULL, str, session_removed, session); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); + return false; + } + + snprintf(str, sizeof(str), fmt, "Session", "PauseDevice", session->path); + ret = sd_bus_add_match(session->bus, NULL, str, pause_device, session); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); + return false; + } + + snprintf(str, sizeof(str), fmt, "Session", "ResumeDevice", session->path); + ret = sd_bus_add_match(session->bus, NULL, str, resume_device, session); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); + return false; + } + + ret = sd_bus_match_signal(session->bus, NULL, "org.freedesktop.login1", + session->path, "org.freedesktop.DBus.Properties", "PropertiesChanged", + properties_changed, session); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); + return false; + } + + return true; +} + +static int dbus_event(int fd, uint32_t mask, void *data) { + sd_bus *bus = data; + while (sd_bus_process(bus, NULL) > 0) { + // Do nothing. + } + return 1; +} + +static struct wlr_session *logind_session_create(struct wl_display *disp) { + int ret; + struct logind_session *session = calloc(1, sizeof(*session)); + if (!session) { + wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno)); + return NULL; + } + + ret = sd_pid_get_session(getpid(), &session->id); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get session id: %s", strerror(-ret)); + goto error; + } + + char *seat; + ret = sd_session_get_seat(session->id, &seat); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get seat id: %s", strerror(-ret)); + goto error; + } + snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat); + + if (strcmp(seat, "seat0") == 0) { + ret = sd_session_get_vt(session->id, &session->base.vtnr); + if (ret < 0) { + wlr_log(WLR_ERROR, "Session not running in virtual terminal"); + goto error; + } + } + free(seat); + + ret = sd_bus_default_system(&session->bus); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to open D-Bus connection: %s", strerror(-ret)); + goto error; + } + + if (!find_session_path(session)) { + sd_bus_unref(session->bus); + goto error; + } + + if (!add_signal_matches(session)) { + goto error_bus; + } + + struct wl_event_loop *event_loop = wl_display_get_event_loop(disp); + session->event = wl_event_loop_add_fd(event_loop, sd_bus_get_fd(session->bus), + WL_EVENT_READABLE, dbus_event, session->bus); + + if (!session_activate(session)) { + goto error_bus; + } + + if (!take_control(session)) { + goto error_bus; + } + + wlr_log(WLR_INFO, "Successfully loaded logind session"); + + session->base.impl = &session_logind; + return &session->base; + +error_bus: + sd_bus_unref(session->bus); + free(session->path); + +error: + free(session->id); + return NULL; +} + +const struct session_impl session_logind = { + .create = logind_session_create, + .destroy = logind_session_destroy, + .open = logind_take_device, + .close = logind_release_device, + .change_vt = logind_change_vt, +}; diff --git a/backend/session/session.c b/backend/session/session.c new file mode 100644 index 00000000..96cde65c --- /dev/null +++ b/backend/session/session.c @@ -0,0 +1,359 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <libudev.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <wayland-server.h> +#include <wlr/backend/session.h> +#include <wlr/backend/session/interface.h> +#include <wlr/config.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include "util/signal.h" + +extern const struct session_impl session_logind; +extern const struct session_impl session_direct; + +static const struct session_impl *impls[] = { +#if WLR_HAS_SYSTEMD || WLR_HAS_ELOGIND + &session_logind, +#endif + &session_direct, + NULL, +}; + +static int udev_event(int fd, uint32_t mask, void *data) { + struct wlr_session *session = data; + + struct udev_device *udev_dev = udev_monitor_receive_device(session->mon); + if (!udev_dev) { + return 1; + } + + const char *action = udev_device_get_action(udev_dev); + + wlr_log(WLR_DEBUG, "udev event for %s (%s)", + udev_device_get_sysname(udev_dev), action); + + if (!action || strcmp(action, "change") != 0) { + goto out; + } + + dev_t devnum = udev_device_get_devnum(udev_dev); + struct wlr_device *dev; + + wl_list_for_each(dev, &session->devices, link) { + if (dev->dev == devnum) { + wlr_signal_emit_safe(&dev->signal, session); + break; + } + } + +out: + udev_device_unref(udev_dev); + return 1; +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_session *session = + wl_container_of(listener, session, display_destroy); + wlr_session_destroy(session); +} + +struct wlr_session *wlr_session_create(struct wl_display *disp) { + struct wlr_session *session = NULL; + + const char *env_wlr_session = getenv("WLR_SESSION"); + if (env_wlr_session) { + if (!strcmp(env_wlr_session, "logind") || !strcmp(env_wlr_session, "systemd")) { + #if WLR_HAS_SYSTEMD || WLR_HAS_ELOGIND + session = session_logind.create(disp); + #else + wlr_log(WLR_ERROR, "wlroots is not compiled with logind support"); + #endif + } else if (!strcmp(env_wlr_session, "direct")) { + session = session_direct.create(disp); + } else { + wlr_log(WLR_ERROR, "WLR_SESSION has an invalid value: %s", env_wlr_session); + } + } else { + const struct session_impl **iter; + for (iter = impls; !session && *iter; ++iter) { + session = (*iter)->create(disp); + } + } + + if (!session) { + wlr_log(WLR_ERROR, "Failed to load session backend"); + return NULL; + } + + session->active = true; + wl_signal_init(&session->session_signal); + wl_signal_init(&session->events.destroy); + wl_list_init(&session->devices); + + session->udev = udev_new(); + if (!session->udev) { + wlr_log_errno(WLR_ERROR, "Failed to create udev context"); + goto error_session; + } + + session->mon = udev_monitor_new_from_netlink(session->udev, "udev"); + if (!session->mon) { + wlr_log_errno(WLR_ERROR, "Failed to create udev monitor"); + goto error_udev; + } + + udev_monitor_filter_add_match_subsystem_devtype(session->mon, "drm", NULL); + udev_monitor_enable_receiving(session->mon); + + struct wl_event_loop *event_loop = wl_display_get_event_loop(disp); + int fd = udev_monitor_get_fd(session->mon); + + session->udev_event = wl_event_loop_add_fd(event_loop, fd, + WL_EVENT_READABLE, udev_event, session); + if (!session->udev_event) { + wlr_log_errno(WLR_ERROR, "Failed to create udev event source"); + goto error_mon; + } + + session->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(disp, &session->display_destroy); + + return session; + +error_mon: + udev_monitor_unref(session->mon); +error_udev: + udev_unref(session->udev); +error_session: + session->impl->destroy(session); + return NULL; +} + +void wlr_session_destroy(struct wlr_session *session) { + if (!session) { + return; + } + + wlr_signal_emit_safe(&session->events.destroy, session); + wl_list_remove(&session->display_destroy.link); + + wl_event_source_remove(session->udev_event); + udev_monitor_unref(session->mon); + udev_unref(session->udev); + + session->impl->destroy(session); +} + +int wlr_session_open_file(struct wlr_session *session, const char *path) { + int fd = session->impl->open(session, path); + if (fd < 0) { + return fd; + } + + struct wlr_device *dev = malloc(sizeof(*dev)); + if (!dev) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error; + } + + struct stat st; + if (fstat(fd, &st) < 0) { + wlr_log_errno(WLR_ERROR, "Stat failed"); + goto error; + } + + dev->fd = fd; + dev->dev = st.st_rdev; + wl_signal_init(&dev->signal); + wl_list_insert(&session->devices, &dev->link); + + return fd; + +error: + free(dev); + return fd; +} + +static struct wlr_device *find_device(struct wlr_session *session, int fd) { + struct wlr_device *dev; + + wl_list_for_each(dev, &session->devices, link) { + if (dev->fd == fd) { + return dev; + } + } + + wlr_log(WLR_ERROR, "Tried to use fd %d not opened by session", fd); + assert(0); +} + +void wlr_session_close_file(struct wlr_session *session, int fd) { + struct wlr_device *dev = find_device(session, fd); + + session->impl->close(session, fd); + wl_list_remove(&dev->link); + free(dev); +} + +void wlr_session_signal_add(struct wlr_session *session, int fd, + struct wl_listener *listener) { + struct wlr_device *dev = find_device(session, fd); + + wl_signal_add(&dev->signal, listener); +} + +bool wlr_session_change_vt(struct wlr_session *session, unsigned vt) { + if (!session) { + return false; + } + + return session->impl->change_vt(session, vt); +} + +/* Tests if 'path' is KMS compatible by trying to open it. + * It leaves the open device in *fd_out it it succeeds. + */ +static int open_if_kms(struct wlr_session *restrict session, const char *restrict path) { + int fd; + + if (!path) { + return -1; + } + + fd = wlr_session_open_file(session, path); + if (fd < 0) { + return -1; + } + + drmModeRes *res = drmModeGetResources(fd); + if (!res) { + goto out_fd; + } + + drmModeFreeResources(res); + return fd; + +out_fd: + wlr_session_close_file(session, fd); + return -1; +} + +static size_t explicit_find_gpus(struct wlr_session *session, + size_t ret_len, int ret[static ret_len], const char *str) { + char *gpus = strdup(str); + if (!gpus) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return 0; + } + + size_t i = 0; + char *save; + char *ptr = strtok_r(gpus, ":", &save); + do { + if (i >= ret_len) { + break; + } + + ret[i] = open_if_kms(session, ptr); + if (ret[i] < 0) { + wlr_log(WLR_ERROR, "Unable to open %s as DRM device", ptr); + } else { + ++i; + } + } while ((ptr = strtok_r(NULL, ":", &save))); + + free(gpus); + return i; +} + +/* Tries to find the primary GPU by checking for the "boot_vga" attribute. + * If it's not found, it returns the first valid GPU it finds. + */ +size_t wlr_session_find_gpus(struct wlr_session *session, + size_t ret_len, int *ret) { + const char *explicit = getenv("WLR_DRM_DEVICES"); + if (explicit) { + return explicit_find_gpus(session, ret_len, ret, explicit); + } + +#ifdef __FreeBSD__ + // XXX: libudev-devd does not return any GPUs (yet?) + return explicit_find_gpus(session, ret_len, ret, "/dev/drm/0"); +#else + + struct udev_enumerate *en = udev_enumerate_new(session->udev); + if (!en) { + wlr_log(WLR_ERROR, "Failed to create udev enumeration"); + return -1; + } + + udev_enumerate_add_match_subsystem(en, "drm"); + udev_enumerate_add_match_sysname(en, "card[0-9]*"); + udev_enumerate_scan_devices(en); + + struct udev_list_entry *entry; + size_t i = 0; + + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(en)) { + if (i == ret_len) { + break; + } + + bool is_boot_vga = false; + + const char *path = udev_list_entry_get_name(entry); + struct udev_device *dev = udev_device_new_from_syspath(session->udev, path); + if (!dev) { + continue; + } + + const char *seat = udev_device_get_property_value(dev, "ID_SEAT"); + if (!seat) { + seat = "seat0"; + } + if (session->seat[0] && strcmp(session->seat, seat) != 0) { + udev_device_unref(dev); + continue; + } + + // This is owned by 'dev', so we don't need to free it + struct udev_device *pci = + udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL); + + if (pci) { + const char *id = udev_device_get_sysattr_value(pci, "boot_vga"); + if (id && strcmp(id, "1") == 0) { + is_boot_vga = true; + } + } + + int fd = open_if_kms(session, udev_device_get_devnode(dev)); + if (fd < 0) { + udev_device_unref(dev); + continue; + } + + udev_device_unref(dev); + + ret[i] = fd; + if (is_boot_vga) { + int tmp = ret[0]; + ret[0] = ret[i]; + ret[i] = tmp; + } + + ++i; + } + + udev_enumerate_unref(en); + + return i; +#endif +} |