X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=src%2Flogin%2Flogind-session-device.c;h=50e20f96145e78fe45ae627a421341572275ebc7;hb=7cc655f99a364e6762a50ce25864979b3a52a73f;hp=c2de862250ef696c9a963bb5d403974bda7cf368;hpb=8dbce34b0373923c7aa7d795024bbedb0a85c7ea;p=elogind.git diff --git a/src/login/logind-session-device.c b/src/login/logind-session-device.c index c2de86225..50e20f961 100644 --- a/src/login/logind-session-device.c +++ b/src/login/logind-session-device.c @@ -1,39 +1,25 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ +/* SPDX-License-Identifier: LGPL-2.1+ */ -/*** - This file is part of systemd. - - Copyright 2013 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include #include -#include #include -#include #include #include -#include #include -#include -#include "util.h" -#include "missing.h" +#if 0 /// elogind needs the systems udev header +#include "libudev.h" +#else +#include +#endif // 0 + +#include "alloc-util.h" #include "bus-util.h" +#include "fd-util.h" #include "logind-session-device.h" +#include "missing.h" +#include "parse-util.h" +#include "sd-daemon.h" +#include "util.h" enum SessionDeviceNotifications { SESSION_DEVICE_RESUME, @@ -43,7 +29,7 @@ enum SessionDeviceNotifications { }; static int session_device_notify(SessionDevice *sd, enum SessionDeviceNotifications type) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *path = NULL; const char *t = NULL; uint32_t major, minor; @@ -74,20 +60,25 @@ static int session_device_notify(SessionDevice *sd, enum SessionDeviceNotificati return r; switch (type) { + case SESSION_DEVICE_RESUME: r = sd_bus_message_append(m, "uuh", major, minor, sd->fd); if (r < 0) return r; break; + case SESSION_DEVICE_TRY_PAUSE: t = "pause"; break; + case SESSION_DEVICE_PAUSE: t = "force"; break; + case SESSION_DEVICE_RELEASE: t = "gone"; break; + default: return -EINVAL; } @@ -101,43 +92,33 @@ static int session_device_notify(SessionDevice *sd, enum SessionDeviceNotificati return sd_bus_send(sd->session->manager->bus, m, NULL); } -static int sd_eviocrevoke(int fd) { - static bool warned; - int r; +static void sd_eviocrevoke(int fd) { + static bool warned = false; assert(fd >= 0); - r = ioctl(fd, EVIOCREVOKE, NULL); - if (r < 0) { - r = -errno; - if (r == -EINVAL && !warned) { + if (ioctl(fd, EVIOCREVOKE, NULL) < 0) { + + if (errno == EINVAL && !warned) { + log_warning_errno(errno, "Kernel does not support evdev-revocation: %m"); warned = true; - log_warning("kernel does not support evdev-revocation"); } } - - return 0; } static int sd_drmsetmaster(int fd) { - int r; - assert(fd >= 0); - r = ioctl(fd, DRM_IOCTL_SET_MASTER, 0); - if (r < 0) + if (ioctl(fd, DRM_IOCTL_SET_MASTER, 0) < 0) return -errno; return 0; } static int sd_drmdropmaster(int fd) { - int r; - assert(fd >= 0); - r = ioctl(fd, DRM_IOCTL_DROP_MASTER, 0); - if (r < 0) + if (ioctl(fd, DRM_IOCTL_DROP_MASTER, 0) < 0) return -errno; return 0; @@ -146,7 +127,9 @@ static int sd_drmdropmaster(int fd) { static int session_device_open(SessionDevice *sd, bool active) { int fd, r; + assert(sd); assert(sd->type != DEVICE_TYPE_UNKNOWN); + assert(sd->node); /* open device and try to get an udev_device from it */ fd = open(sd->node, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); @@ -154,28 +137,27 @@ static int session_device_open(SessionDevice *sd, bool active) { return -errno; switch (sd->type) { + case DEVICE_TYPE_DRM: if (active) { - /* Weird legacy DRM semantics might return an error - * even though we're master. No way to detect that so - * fail at all times and let caller retry in inactive - * state. */ + /* Weird legacy DRM semantics might return an error even though we're master. No way to detect + * that so fail at all times and let caller retry in inactive state. */ r = sd_drmsetmaster(fd); if (r < 0) { close_nointr(fd); return r; } - } else { - /* DRM-Master is granted to the first user who opens a - * device automatically (ughh, racy!). Hence, we just - * drop DRM-Master in case we were the first. */ - sd_drmdropmaster(fd); - } + } else + /* DRM-Master is granted to the first user who opens a device automatically (ughh, + * racy!). Hence, we just drop DRM-Master in case we were the first. */ + (void) sd_drmdropmaster(fd); break; + case DEVICE_TYPE_EVDEV: if (!active) sd_eviocrevoke(fd); break; + case DEVICE_TYPE_UNKNOWN: default: /* fallback for devices wihout synchronizations */ @@ -195,26 +177,35 @@ static int session_device_start(SessionDevice *sd) { return 0; switch (sd->type) { + case DEVICE_TYPE_DRM: - /* Device is kept open. Simply call drmSetMaster() and hope - * there is no-one else. In case it fails, we keep the device - * paused. Maybe at some point we have a drmStealMaster(). */ + if (sd->fd < 0) { + log_error("Failed to re-activate DRM fd, as the fd was lost (maybe logind restart went wrong?)"); + return -EBADF; + } + + /* Device is kept open. Simply call drmSetMaster() and hope there is no-one else. In case it fails, we + * keep the device paused. Maybe at some point we have a drmStealMaster(). */ r = sd_drmsetmaster(sd->fd); if (r < 0) return r; break; + case DEVICE_TYPE_EVDEV: - /* Evdev devices are revoked while inactive. Reopen it and we - * are fine. */ + /* Evdev devices are revoked while inactive. Reopen it and we are fine. */ r = session_device_open(sd, true); if (r < 0) return r; - close_nointr(sd->fd); + + /* For evdev devices, the file descriptor might be left uninitialized. This might happen while resuming + * into a session and logind has been restarted right before. */ + safe_close(sd->fd); sd->fd = r; break; + case DEVICE_TYPE_UNKNOWN: default: - /* fallback for devices wihout synchronizations */ + /* fallback for devices without synchronizations */ break; } @@ -229,13 +220,20 @@ static void session_device_stop(SessionDevice *sd) { return; switch (sd->type) { + case DEVICE_TYPE_DRM: + if (sd->fd < 0) { + log_error("Failed to de-activate DRM fd, as the fd was lost (maybe logind restart went wrong?)"); + return; + } + /* On DRM devices we simply drop DRM-Master but keep it open. * This allows the user to keep resources allocated. The * CAP_SYS_ADMIN restriction to DRM-Master prevents users from * circumventing this. */ sd_drmdropmaster(sd->fd); break; + case DEVICE_TYPE_EVDEV: /* Revoke access on evdev file-descriptors during deactivation. * This will basically prevent any operations on the fd and @@ -243,6 +241,7 @@ static void session_device_stop(SessionDevice *sd) { * protection this way. */ sd_eviocrevoke(sd->fd); break; + case DEVICE_TYPE_UNKNOWN: default: /* fallback for devices without synchronization */ @@ -344,7 +343,7 @@ err_dev: return r; } -int session_device_new(Session *s, dev_t dev, SessionDevice **out) { +int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out) { SessionDevice *sd; int r; @@ -368,27 +367,27 @@ int session_device_new(Session *s, dev_t dev, SessionDevice **out) { goto error; r = hashmap_put(s->devices, &sd->dev, sd); - if (r < 0) { - r = -ENOMEM; + if (r < 0) goto error; - } - /* Open the device for the first time. We need a valid fd to pass back - * to the caller. If the session is not active, this _might_ immediately - * revoke access and thus invalidate the fd. But this is still needed - * to pass a valid fd back. */ - sd->active = session_is_active(s); - r = session_device_open(sd, sd->active); - if (r < 0) { - /* EINVAL _may_ mean a master is active; retry inactive */ - if (sd->active && r == -EINVAL) { - sd->active = false; - r = session_device_open(sd, false); + if (open_device) { + /* Open the device for the first time. We need a valid fd to pass back + * to the caller. If the session is not active, this _might_ immediately + * revoke access and thus invalidate the fd. But this is still needed + * to pass a valid fd back. */ + sd->active = session_is_active(s); + r = session_device_open(sd, sd->active); + if (r < 0) { + /* EINVAL _may_ mean a master is active; retry inactive */ + if (sd->active && r == -EINVAL) { + sd->active = false; + r = session_device_open(sd, false); + } + if (r < 0) + goto error; } - if (r < 0) - goto error; + sd->fd = r; } - sd->fd = r; LIST_PREPEND(sd_by_device, sd->device->session_devices, sd); @@ -405,9 +404,26 @@ error: void session_device_free(SessionDevice *sd) { assert(sd); + /* Make sure to remove the pushed fd. */ + if (sd->pushed_fd) { + _cleanup_free_ char *m = NULL; + const char *id; + int r; + + /* Session ID does not contain separators. */ + id = sd->session->id; + assert(*(id + strcspn(id, "-\n")) == '\0'); + + r = asprintf(&m, "FDSTOREREMOVE=1\n" + "FDNAME=session-%s-device-%u-%u\n", + id, major(sd->dev), minor(sd->dev)); + if (r >= 0) + (void) sd_notify(false, m); + } + session_device_stop(sd); session_device_notify(sd, SESSION_DEVICE_RELEASE); - close_nointr(sd->fd); + safe_close(sd->fd); LIST_REMOVE(sd_by_device, sd->device->session_devices, sd); @@ -438,16 +454,19 @@ void session_device_complete_pause(SessionDevice *sd) { void session_device_resume_all(Session *s) { SessionDevice *sd; Iterator i; - int r; assert(s); HASHMAP_FOREACH(sd, s->devices, i) { - if (!sd->active) { - r = session_device_start(sd); - if (!r) - session_device_notify(sd, SESSION_DEVICE_RESUME); - } + if (sd->active) + continue; + + if (session_device_start(sd) < 0) + continue; + if (session_device_save(sd) < 0) + continue; + + session_device_notify(sd, SESSION_DEVICE_RESUME); } } @@ -458,26 +477,74 @@ void session_device_pause_all(Session *s) { assert(s); HASHMAP_FOREACH(sd, s->devices, i) { - if (sd->active) { - session_device_stop(sd); - session_device_notify(sd, SESSION_DEVICE_PAUSE); - } + if (!sd->active) + continue; + + session_device_stop(sd); + session_device_notify(sd, SESSION_DEVICE_PAUSE); } } unsigned int session_device_try_pause_all(Session *s) { + unsigned num_pending = 0; SessionDevice *sd; Iterator i; - unsigned int num_pending = 0; assert(s); HASHMAP_FOREACH(sd, s->devices, i) { - if (sd->active) { - session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE); - ++num_pending; - } + if (!sd->active) + continue; + + session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE); + num_pending++; } return num_pending; } + +int session_device_save(SessionDevice *sd) { + _cleanup_free_ char *m = NULL; + const char *id; + int r; + + assert(sd); + + /* Store device fd in PID1. It will send it back to us on restart so revocation will continue to work. To make + * things simple, send fds for all type of devices even if they don't support the revocation mechanism so we + * don't have to handle them differently later. + * + * Note: for device supporting revocation, PID1 will drop a stored fd automatically if the corresponding device + * is revoked. */ + + if (sd->pushed_fd) + return 0; + + /* Session ID does not contain separators. */ + id = sd->session->id; + assert(*(id + strcspn(id, "-\n")) == '\0'); + + r = asprintf(&m, "FDSTORE=1\n" + "FDNAME=session-%s-device-%u-%u\n", + id, major(sd->dev), minor(sd->dev)); + if (r < 0) + return r; + + r = sd_pid_notify_with_fds(0, false, m, &sd->fd, 1); + if (r < 0) + return r; + + sd->pushed_fd = true; + return 1; +} + +void session_device_attach_fd(SessionDevice *sd, int fd, bool active) { + assert(fd >= 0); + assert(sd); + assert(sd->fd < 0); + assert(!sd->active); + + sd->fd = fd; + sd->pushed_fd = true; + sd->active = active; +}