chiark / gitweb /
tree-wide: drop copyright headers from frequent contributors
[elogind.git] / src / login / logind-session-device.c
index c2de862250ef696c9a963bb5d403974bda7cf368..50e20f96145e78fe45ae627a421341572275ebc7 100644 (file)
@@ -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 <http://www.gnu.org/licenses/>.
-***/
-
-#include <assert.h>
 #include <fcntl.h>
-#include <libudev.h>
 #include <linux/input.h>
-#include <linux/ioctl.h>
 #include <string.h>
 #include <sys/ioctl.h>
-#include <sys/stat.h>
 #include <sys/types.h>
-#include <unistd.h>
 
-#include "util.h"
-#include "missing.h"
+#if 0 /// elogind needs the systems udev header
+#include "libudev.h"
+#else
+#include <libudev.h>
+#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;
+}