[systemd-commits] 3 commits - Makefile.am TODO src/login src/shared

Lennart Poettering lennart at kemper.freedesktop.org
Tue Sep 17 15:17:11 PDT 2013


 Makefile.am                       |    2 
 TODO                              |    2 
 src/login/logind-device.c         |    4 
 src/login/logind-device.h         |    2 
 src/login/logind-seat.c           |   22 +
 src/login/logind-seat.h           |    2 
 src/login/logind-session-dbus.c   |  126 +++++++++
 src/login/logind-session-device.c |  479 ++++++++++++++++++++++++++++++++++++++
 src/login/logind-session-device.h |   60 ++++
 src/login/logind-session.c        |   58 ++++
 src/login/logind-session.h        |    2 
 src/shared/missing.h              |   13 +
 12 files changed, 766 insertions(+), 6 deletions(-)

New commits:
commit d7bd01b547bd91353513131561de9cc7d9f7d405
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Tue Sep 17 23:40:19 2013 +0200

    logind: implement generic multi-session
    
    This enables the multi-session capability for seats that don't have VTs.
    For legacy seats with VTs, everything stays the same. However, all other
    seats now also get the multi-session capability.
    
    The only feature that was missing was session-switching. As logind can
    force a session-switch and signal that via the "Active" property, we only
    need a way to allow synchronized/delayed session switches. Compositors
    need to cleanup some devices before acknowledging the session switch.
    Therefore, we use the session-devices to give compositors a chance to
    block a session-switch until they cleaned everything up.
    
    If you activate a session on a seat without VTs, we send a PauseDevice
    signal to the active session for every active device. Only once the
    session acknowledged all these with a PauseDeviceComplete() call, we
    perform the final session switch.
    
    One important note is that delayed session-switching is meant for
    backwards compatibility. New compositors or other sessions should really
    try to deal correctly with forced session switches! They only need to
    handle EACCES/EPERM from syscalls and treat them as "PauseDevice" signal.
    
    Following logind patches will add a timeout to session-switches which
    forces the switch if the active session does not react in a timely
    fashion. Moreover, explicit ForceActivate() calls might also be supported.
    Hence, sessions must not crash if their devices get paused.

diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c
index f88738a..4a4d40a 100644
--- a/src/login/logind-seat.c
+++ b/src/login/logind-seat.c
@@ -425,6 +425,21 @@ int seat_attach_session(Seat *s, Session *session) {
         return 0;
 }
 
+void seat_complete_switch(Seat *s) {
+        Session *session;
+
+        assert(s);
+
+        /* if no session-switch is pending or if it got canceled, do nothing */
+        if (!s->pending_switch)
+                return;
+
+        session = s->pending_switch;
+        s->pending_switch = NULL;
+
+        seat_set_active(s, session);
+}
+
 bool seat_has_vts(Seat *s) {
         assert(s);
 
diff --git a/src/login/logind-seat.h b/src/login/logind-seat.h
index d3438b8..be6db6e 100644
--- a/src/login/logind-seat.h
+++ b/src/login/logind-seat.h
@@ -38,6 +38,7 @@ struct Seat {
         LIST_HEAD(Device, devices);
 
         Session *active;
+        Session *pending_switch;
         LIST_HEAD(Session, sessions);
 
         bool in_gc_queue:1;
@@ -59,6 +60,7 @@ int seat_read_active_vt(Seat *s);
 int seat_preallocate_vts(Seat *s);
 
 int seat_attach_session(Seat *s, Session *session);
+void seat_complete_switch(Seat *s);
 
 bool seat_has_vts(Seat *s);
 bool seat_is_seat0(Seat *s);
diff --git a/src/login/logind-session-device.c b/src/login/logind-session-device.c
index 80fd364..e92bb54 100644
--- a/src/login/logind-session-device.c
+++ b/src/login/logind-session-device.c
@@ -414,10 +414,21 @@ void session_device_free(SessionDevice *sd) {
 }
 
 void session_device_complete_pause(SessionDevice *sd) {
+        SessionDevice *iter;
+        Iterator i;
+
         if (!sd->active)
                 return;
 
         session_device_stop(sd);
+
+        /* if not all devices are paused, wait for further completion events */
+        HASHMAP_FOREACH(iter, sd->session->devices, i)
+                if (iter->active)
+                        return;
+
+        /* complete any pending session switch */
+        seat_complete_switch(sd->session->seat);
 }
 
 void session_device_resume_all(Session *s) {
@@ -449,3 +460,20 @@ void session_device_pause_all(Session *s) {
                 }
         }
 }
+
+unsigned int session_device_try_pause_all(Session *s) {
+        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;
+                }
+        }
+
+        return num_pending;
+}
diff --git a/src/login/logind-session-device.h b/src/login/logind-session-device.h
index 511fce0..eac7ca7 100644
--- a/src/login/logind-session-device.h
+++ b/src/login/logind-session-device.h
@@ -57,3 +57,4 @@ void session_device_complete_pause(SessionDevice *sd);
 
 void session_device_resume_all(Session *s);
 void session_device_pause_all(Session *s);
+unsigned int session_device_try_pause_all(Session *s);
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
index fcc1901..eea0bfb 100644
--- a/src/login/logind-session.c
+++ b/src/login/logind-session.c
@@ -100,6 +100,8 @@ void session_free(Session *s) {
         if (s->seat) {
                 if (s->seat->active == s)
                         s->seat->active = NULL;
+                if (s->seat->pending_switch == s)
+                        s->seat->pending_switch = NULL;
 
                 LIST_REMOVE(Session, sessions_by_seat, s->seat->sessions, s);
         }
@@ -375,21 +377,40 @@ int session_load(Session *s) {
 }
 
 int session_activate(Session *s) {
+        unsigned int num_pending;
+
         assert(s);
         assert(s->user);
 
-        if (s->vtnr <= 0)
-                return -ENOTSUP;
-
         if (!s->seat)
                 return -ENOTSUP;
 
         if (s->seat->active == s)
                 return 0;
 
-        assert(seat_has_vts(s->seat));
+        /* on seats with VTs, we let VTs manage session-switching */
+        if (seat_has_vts(s->seat)) {
+                if (s->vtnr <= 0)
+                        return -ENOTSUP;
+
+                return chvt(s->vtnr);
+        }
 
-        return chvt(s->vtnr);
+        /* On seats without VTs, we implement session-switching in logind. We
+         * try to pause all session-devices and wait until the session
+         * controller acknowledged them. Once all devices are asleep, we simply
+         * switch the active session and be done.
+         * We save the session we want to switch to in seat->pending_switch and
+         * seat_complete_switch() will perform the final switch. */
+
+        s->seat->pending_switch = s;
+
+        /* if no devices are running, immediately perform the session switch */
+        num_pending = session_device_try_pause_all(s);
+        if (!num_pending)
+                seat_complete_switch(s->seat);
+
+        return 0;
 }
 
 static int session_link_x11_socket(Session *s) {

commit 118ecf32425a590ea266b5c2b6de7962bb242356
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Tue Sep 17 23:39:04 2013 +0200

    logind: introduce session-devices
    
    A session-device is a device that is bound to a seat and used by a
    session-controller to run the session. This currently includes DRM, fbdev
    and evdev devices. A session-device can be created via RequestDevice() on
    the dbus API of the session. You can drop it via ReleaseDevice() again.
    Once the session is destroyed or you drop control of the session, all
    session-devices are automatically destroyed.
    
    Session devices follow the session "active" state. A device can be
    active/running or inactive/paused. Whenever a session is not the active
    session, no session-device of it can be active. That is, if a session is
    not in foreground, all session-devices are paused.
    Whenever a session becomes active, all devices are resumed/activated by
    logind. If it fails, a device may stay paused.
    
    With every session-device you request, you also get a file-descriptor
    back. logind keeps a copy of this fd and uses kernel specific calls to
    pause/resume the file-descriptors. For example, a DRM fd is muted
    by logind as long as a given session is not active. Hence, the fd of the
    application is also muted. Once the session gets active, logind unmutes
    the fd and the application will get DRM access again.
    This, however, requires kernel support. DRM devices provide DRM-Master for
    synchronization, evdev devices have EVIOCREVOKE (pending on
    linux-input-ML). fbdev devices do not provide such synchronization methods
    (and never will).
    Note that for evdev devices, we call EVIOCREVOKE once a session gets
    inactive. However, this cannot be undone (the fd is still valid but mostly
    unusable). So we reopen a new fd once the session is activated and send it
    together with the ResumeDevice() signal.
    
    With this infrastructure in place, compositors can now run without
    CAP_SYS_ADMIN (that is, without being root). They use RequestControl() to
    acquire a session and listen for devices via udev_monitor. For every
    device they want to open, they call RequestDevice() on logind. This
    returns a fd which they can use now. They no longer have to open the
    devices themselves or call any privileged ioctls. This is all done by
    logind.
    Session-switches are still bound to VTs. Hence, compositors will get
    notified via the usual VT mechanisms and can cleanup their state. Once the
    VT switch is acknowledged as usual, logind will get notified via sysfs and
    pause the old-session's devices and resume the devices of the new session.
    
    To allow using this infrastructure with systems without VTs, we provide
    notification signals. logind sends PauseDevice("force") dbus signals to
    the current session controller for every device that it pauses. And it
    sends ResumeDevice signals for every device that it resumes. For
    seats with VTs this is sent _after_ the VT switch is acknowledged. Because
    the compositor already acknowledged that it cleaned-up all devices.
    However, for seats without VTs, this is used to notify the active
    compositor that the session is about to be deactivated. That is, logind
    sends PauseDevice("force") for each active device and then performs the
    session-switch. The session-switch changes the "Active" property of the
    session which can be monitored by the compositor. The new session is
    activated and the ResumeDevice events are sent.
    
    For seats without VTs, this is a forced session-switch. As this is not
    backwards-compatible (xserver actually crashes, weston drops the related
    devices, ..) we also provide an acknowledged session-switch. Note that
    this is never used for sessions with VTs. You use the acknowledged
    VT-switch on these seats.
    
    An acknowledged session switch sends PauseDevice("pause") instead of
    PauseDevice("force") to the active session. It schedules a short timeout
    and waits for the session to acknowledge each of them with
    PauseDeviceComplete(). Once all are acknowledged, or the session ran out
    of time, a PauseDevice("force") is sent for all remaining active devices
    and the session switch is performed.
    Note that this is only partially implemented, yet, as we don't allow
    multi-session without VTs, yet. A follow up commit will hook it up and
    implemented the acknowledgements+timeout.
    
    The implementation is quite simple. We use major/minor exclusively to
    identify devices on the bus. On RequestDevice() we retrieve the
    udev_device from the major/minor and search for an existing "Device"
    object. If no exists, we create it. This guarantees us that we are
    notified whenever the device changes seats or is removed.
    
    We create a new SessionDevice object and link it to the related Session
    and Device. Session->devices is a hashtable to lookup SessionDevice
    objects via major/minor. Device->session_devices is a linked list so we
    can release all linked session-devices once a device vanishes.
    
    Now we only have to hook this up in seat_set_active() so we correctly
    change device states during session-switches. As mentioned earlier, these
    are forced state-changes as VTs are currently used exclusively for
    multi-session implementations.
    
    Everything else are hooks to release all session-devices once the
    controller changes or a session is closed or removed.

diff --git a/Makefile.am b/Makefile.am
index 49c1a1d..9b06c28 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3768,6 +3768,8 @@ libsystemd_logind_core_la_SOURCES = \
 	src/login/logind-seat.h \
 	src/login/logind-session.c \
 	src/login/logind-session.h \
+	src/login/logind-session-device.c \
+	src/login/logind-session-device.h \
 	src/login/logind-user.c \
 	src/login/logind-user.h \
 	src/login/logind-inhibit.c \
diff --git a/src/login/logind-device.c b/src/login/logind-device.c
index 2bcd6a1..95c2307 100644
--- a/src/login/logind-device.c
+++ b/src/login/logind-device.c
@@ -67,11 +67,15 @@ void device_free(Device *d) {
 
 void device_detach(Device *d) {
         Seat *s;
+        SessionDevice *sd;
         assert(d);
 
         if (!d->seat)
                 return;
 
+        while ((sd = d->session_devices))
+                session_device_free(sd);
+
         s = d->seat;
         LIST_REMOVE(Device, devices, d->seat->devices, d);
         d->seat = NULL;
diff --git a/src/login/logind-device.h b/src/login/logind-device.h
index 315f0e6..fa6eda7 100644
--- a/src/login/logind-device.h
+++ b/src/login/logind-device.h
@@ -27,6 +27,7 @@ typedef struct Device Device;
 #include "util.h"
 #include "logind.h"
 #include "logind-seat.h"
+#include "logind-session-device.h"
 
 struct Device {
         Manager *manager;
@@ -38,6 +39,7 @@ struct Device {
         dual_timestamp timestamp;
 
         LIST_FIELDS(struct Device, devices);
+        LIST_HEAD(SessionDevice, session_devices);
 };
 
 Device* device_new(Manager *m, const char *sysfs, bool master);
diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c
index 3dc529b..f88738a 100644
--- a/src/login/logind-seat.c
+++ b/src/login/logind-seat.c
@@ -246,10 +246,15 @@ int seat_set_active(Seat *s, Session *session) {
         old_active = s->active;
         s->active = session;
 
+        if (old_active)
+                session_device_pause_all(old_active);
+
         seat_apply_acls(s, old_active);
 
-        if (session && session->started)
+        if (session && session->started) {
                 session_send_changed(session, "Active\0");
+                session_device_resume_all(session);
+        }
 
         if (!session || session->started)
                 seat_send_changed(s, "ActiveSession\0");
diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c
index 35bf448..f793f99 100644
--- a/src/login/logind-session-dbus.c
+++ b/src/login/logind-session-dbus.c
@@ -24,6 +24,7 @@
 
 #include "logind.h"
 #include "logind-session.h"
+#include "logind-session-device.h"
 #include "dbus-common.h"
 #include "util.h"
 
@@ -44,6 +45,30 @@
         "   <arg name=\"force\" type=\"b\"/>\n"                         \
         "  </method>\n"                                                 \
         "  <method name=\"ReleaseControl\"/>\n"                         \
+        "  <method name=\"TakeDevice\">\n"                              \
+        "   <arg name=\"major\" type=\"u\" direction=\"in\"/>\n"        \
+        "   <arg name=\"minor\" type=\"u\" direction=\"in\"/>\n"        \
+        "   <arg name=\"fd\" type=\"h\" direction=\"out\"/>\n"          \
+        "   <arg name=\"paused\" type=\"b\" direction=\"out\"/>\n"      \
+        "  </method>\n"                                                 \
+        "  <method name=\"ReleaseDevice\">\n"                           \
+        "   <arg name=\"major\" type=\"u\"/>\n"                         \
+        "   <arg name=\"minor\" type=\"u\"/>\n"                         \
+        "  </method>\n"                                                 \
+        "  <method name=\"PauseDeviceComplete\">\n"                     \
+        "   <arg name=\"major\" type=\"u\"/>\n"                         \
+        "   <arg name=\"minor\" type=\"u\"/>\n"                         \
+        "  </method>\n"                                                 \
+        "  <signal name=\"PauseDevice\">\n"                             \
+        "   <arg name=\"major\" type=\"u\"/>\n"                         \
+        "   <arg name=\"minor\" type=\"u\"/>\n"                         \
+        "   <arg name=\"type\" type=\"s\"/>\n"                          \
+        "  </signal>\n"                                                 \
+        "  <signal name=\"ResumeDevice\">\n"                            \
+        "   <arg name=\"major\" type=\"u\"/>\n"                         \
+        "   <arg name=\"minor\" type=\"u\"/>\n"                         \
+        "   <arg name=\"fd\" type=\"h\"/>\n"                            \
+        "  </signal>\n"                                                 \
         "  <signal name=\"Lock\"/>\n"                                   \
         "  <signal name=\"Unlock\"/>\n"                                 \
         "  <property name=\"Id\" type=\"s\" access=\"read\"/>\n"        \
@@ -408,6 +433,107 @@ static DBusHandlerResult session_message_dispatch(
                 if (!reply)
                         goto oom;
 
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "TakeDevice")) {
+                SessionDevice *sd;
+                bool b;
+                dbus_bool_t paused;
+                uint32_t major, minor;
+                dev_t dev;
+
+                if (!session_is_controller(s, bus_message_get_sender_with_fallback(message)))
+                        return bus_send_error_reply(connection, message, NULL, -EPERM);
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_UINT32, &major,
+                                    DBUS_TYPE_UINT32, &minor,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                dev = makedev(major, minor);
+                assert_cc(sizeof(unsigned long) >= sizeof(dev_t));
+
+                sd = hashmap_get(s->devices, ULONG_TO_PTR((unsigned long)dev));
+                if (sd) {
+                        /* We don't allow retrieving a device multiple times.
+                         * The related ReleaseDevice call is not ref-counted.
+                         * The caller should use dup() if it requires more than
+                         * one fd (it would be functionally equivalent). */
+                        return bus_send_error_reply(connection, message, &error, -EBUSY);
+                }
+
+                r = session_device_new(s, dev, &sd);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply) {
+                        session_device_free(sd);
+                        goto oom;
+                }
+
+                paused = !sd->active;
+                b = dbus_message_append_args(
+                                reply,
+                                DBUS_TYPE_UNIX_FD, &sd->fd,
+                                DBUS_TYPE_BOOLEAN, &paused,
+                                DBUS_TYPE_INVALID);
+                if (!b) {
+                        session_device_free(sd);
+                        return bus_send_error_reply(connection, message, NULL, -ENOMEM);
+                }
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "ReleaseDevice")) {
+                SessionDevice *sd;
+                uint32_t major, minor;
+
+                if (!session_is_controller(s, bus_message_get_sender_with_fallback(message)))
+                        return bus_send_error_reply(connection, message, NULL, -EPERM);
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_UINT32, &major,
+                                    DBUS_TYPE_UINT32, &minor,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                sd = hashmap_get(s->devices, ULONG_TO_PTR((unsigned long)makedev(major, minor)));
+                if (!sd)
+                        return bus_send_error_reply(connection, message, NULL, -ENODEV);
+
+                session_device_free(sd);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "PauseDeviceComplete")) {
+                SessionDevice *sd;
+                uint32_t major, minor;
+
+                if (!session_is_controller(s, bus_message_get_sender_with_fallback(message)))
+                        return bus_send_error_reply(connection, message, NULL, -EPERM);
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_UINT32, &major,
+                                    DBUS_TYPE_UINT32, &minor,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                sd = hashmap_get(s->devices, ULONG_TO_PTR((unsigned long)makedev(major, minor)));
+                if (!sd)
+                        return bus_send_error_reply(connection, message, NULL, -ENODEV);
+
+                session_device_complete_pause(sd);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
         } else {
                 const BusBoundProperties bps[] = {
                         { "org.freedesktop.login1.Session", bus_login_session_properties,      s       },
diff --git a/src/login/logind-session-device.c b/src/login/logind-session-device.c
new file mode 100644
index 0000000..80fd364
--- /dev/null
+++ b/src/login/logind-session-device.c
@@ -0,0 +1,451 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  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 "dbus-common.h"
+#include "logind-session-device.h"
+#include "util.h"
+#include "missing.h"
+
+enum SessionDeviceNotifications {
+        SESSION_DEVICE_RESUME,
+        SESSION_DEVICE_TRY_PAUSE,
+        SESSION_DEVICE_PAUSE,
+        SESSION_DEVICE_RELEASE,
+};
+
+static void session_device_notify(SessionDevice *sd, enum SessionDeviceNotifications type) {
+        _cleanup_dbus_message_unref_ DBusMessage *m = NULL;
+        _cleanup_free_ char *path = NULL;
+        const char *t = NULL;
+
+        assert(sd);
+
+        if (!sd->session->controller)
+                return;
+
+        path = session_bus_path(sd->session);
+        if (!path)
+                return;
+
+        m = dbus_message_new_signal(path,
+                                    "org.freedesktop.login1.Session",
+                                    (type == SESSION_DEVICE_RESUME) ? "ResumeDevice" : "PauseDevice");
+        if (!m)
+                return;
+
+        if (!dbus_message_set_destination(m, sd->session->controller))
+                return;
+
+        switch (type) {
+        case SESSION_DEVICE_RESUME:
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_UINT32, major(sd->dev),
+                                              DBUS_TYPE_UINT32, minor(sd->dev),
+                                              DBUS_TYPE_UNIX_FD, &sd->fd,
+                                              DBUS_TYPE_INVALID))
+                        return;
+                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;
+        }
+
+        if (t && !dbus_message_append_args(m,
+                                           DBUS_TYPE_UINT32, major(sd->dev),
+                                           DBUS_TYPE_UINT32, minor(sd->dev),
+                                           DBUS_TYPE_STRING, &t,
+                                           DBUS_TYPE_INVALID))
+                return;
+
+        dbus_connection_send(sd->session->manager->bus, m, NULL);
+}
+
+static int sd_eviocrevoke(int fd) {
+        static bool warned;
+        int r;
+
+        assert(fd >= 0);
+
+        r = ioctl(fd, EVIOCREVOKE, 1);
+        if (r < 0) {
+                r = -errno;
+                if (r == -EINVAL && !warned) {
+                        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)
+                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)
+                return -errno;
+
+        return 0;
+}
+
+static int session_device_open(SessionDevice *sd, bool active) {
+        int fd;
+
+        assert(sd->type != DEVICE_TYPE_UNKNOWN);
+
+        /* open device and try to get an udev_device from it */
+        fd = open(sd->node, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+        if (fd < 0)
+                return -errno;
+
+        switch (sd->type) {
+        case DEVICE_TYPE_DRM:
+                if (active)
+                        sd_drmsetmaster(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. */
+                        sd_drmdropmaster(fd);
+                }
+                break;
+        case DEVICE_TYPE_EVDEV:
+                if (!active)
+                        sd_eviocrevoke(fd);
+                break;
+        case DEVICE_TYPE_FBDEV:
+        case DEVICE_TYPE_UNKNOWN:
+        default:
+                /* fallback for devices wihout synchronizations */
+                break;
+        }
+
+        return fd;
+}
+
+static int session_device_start(SessionDevice *sd) {
+        int r;
+
+        assert(sd);
+        assert(session_is_active(sd->session));
+
+        if (sd->active)
+                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(). */
+                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. */
+                r = session_device_open(sd, true);
+                if (r < 0)
+                        return r;
+                close_nointr_nofail(sd->fd);
+                sd->fd = r;
+                break;
+        case DEVICE_TYPE_FBDEV:
+                /* fbdev devices have no way to synchronize access. Moreover,
+                 * they mostly operate through mmaps() without any pageflips
+                 * and modesetting, so there is no way for us to prevent access
+                 * but tear down mmaps.
+                 * That would be quite expensive to do on a per-fd context. So
+                 * ignore legcy fbdev and let its users feel the pain they asked
+                 * for when deciding for fbdev. */
+        case DEVICE_TYPE_UNKNOWN:
+        default:
+                /* fallback for devices wihout synchronizations */
+                break;
+        }
+
+        sd->active = true;
+        return 0;
+}
+
+static void session_device_stop(SessionDevice *sd) {
+        assert(sd);
+
+        if (!sd->active)
+                return;
+
+        switch (sd->type) {
+        case DEVICE_TYPE_DRM:
+                /* 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
+                 * cannot be undone. Good side is: it needs no CAP_SYS_ADMIN
+                 * protection this way. */
+                sd_eviocrevoke(sd->fd);
+                break;
+        case DEVICE_TYPE_FBDEV:
+        case DEVICE_TYPE_UNKNOWN:
+        default:
+                /* fallback for devices without synchronization */
+                break;
+        }
+
+        sd->active = false;
+}
+
+static DeviceType detect_device_type(struct udev_device *dev) {
+        const char *sysname, *subsystem;
+        DeviceType type;
+
+        sysname = udev_device_get_sysname(dev);
+        subsystem = udev_device_get_subsystem(dev);
+        type = DEVICE_TYPE_UNKNOWN;
+
+        if (streq_ptr(subsystem, "graphics")) {
+                if (!streq(sysname, "fbcon") && startswith(sysname, "fb"))
+                        type = DEVICE_TYPE_FBDEV;
+        } else if (streq_ptr(subsystem, "drm")) {
+                if (startswith(sysname, "card"))
+                        type = DEVICE_TYPE_DRM;
+        } else if (streq_ptr(subsystem, "input")) {
+                if (startswith(sysname, "event"))
+                        type = DEVICE_TYPE_EVDEV;
+        }
+
+        return type;
+}
+
+static int session_device_verify(SessionDevice *sd) {
+        struct udev_device *dev, *p = NULL;
+        const char *sp, *node;
+        int r;
+
+        dev = udev_device_new_from_devnum(sd->session->manager->udev, 'c', sd->dev);
+        if (!dev)
+                return -ENODEV;
+
+        sp = udev_device_get_syspath(dev);
+        node = udev_device_get_devnode(dev);
+        if (!node) {
+                r = -EINVAL;
+                goto err_dev;
+        }
+
+        /* detect device type so we can find the correct sysfs parent */
+        sd->type = detect_device_type(dev);
+        if (sd->type == DEVICE_TYPE_UNKNOWN) {
+                r = -ENODEV;
+                goto err_dev;
+        } else if (sd->type == DEVICE_TYPE_EVDEV) {
+                /* for evdev devices we need the parent node as device */
+                p = dev;
+                dev = udev_device_get_parent_with_subsystem_devtype(p, "input", NULL);
+                if (!dev) {
+                        r = -ENODEV;
+                        goto err_dev;
+                }
+                sp = udev_device_get_syspath(dev);
+        } else if (sd->type != DEVICE_TYPE_FBDEV &&
+                   sd->type != DEVICE_TYPE_DRM) {
+                /* Prevent opening unsupported devices. Especially devices of
+                 * subsystem "input" must be opened via the evdev node as
+                 * we require EVIOCREVOKE. */
+                r = -ENODEV;
+                goto err_dev;
+        }
+
+        /* search for an existing seat device and return it if available */
+        sd->device = hashmap_get(sd->session->manager->devices, sp);
+        if (!sd->device) {
+                /* The caller might have gotten the udev event before we were
+                 * able to process it. Hence, fake the "add" event and let the
+                 * logind-manager handle the new device. */
+                r = manager_process_seat_device(sd->session->manager, dev);
+                if (r < 0)
+                        goto err_dev;
+
+                /* if it's still not available, then the device is invalid */
+                sd->device = hashmap_get(sd->session->manager->devices, sp);
+                if (!sd->device) {
+                        r = -ENODEV;
+                        goto err_dev;
+                }
+        }
+
+        if (sd->device->seat != sd->session->seat) {
+                r = -EPERM;
+                goto err_dev;
+        }
+
+        sd->node = strdup(node);
+        if (!sd->node) {
+                r = -ENOMEM;
+                goto err_dev;
+        }
+
+        r = 0;
+err_dev:
+        udev_device_unref(p ? : dev);
+        return r;
+}
+
+int session_device_new(Session *s, dev_t dev, SessionDevice **out) {
+        SessionDevice *sd;
+        int r;
+
+        assert(s);
+        assert(out);
+
+        if (!s->seat)
+                return -EPERM;
+
+        sd = new0(SessionDevice, 1);
+        if (!sd)
+                return -ENOMEM;
+
+        sd->session = s;
+        sd->dev = dev;
+        sd->fd = -1;
+        sd->type = DEVICE_TYPE_UNKNOWN;
+
+        r = session_device_verify(sd);
+        if (r < 0)
+                goto error;
+
+        assert_cc(sizeof(unsigned long) >= sizeof(dev_t));
+
+        r = hashmap_put(s->devices, ULONG_TO_PTR((unsigned long)sd->dev), sd);
+        if (r < 0) {
+                r = -ENOMEM;
+                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);
+        sd->fd = session_device_open(sd, sd->active);
+        if (sd->fd < 0)
+                goto error;
+
+        LIST_PREPEND(SessionDevice, sd_by_device, sd->device->session_devices, sd);
+
+        *out = sd;
+        return 0;
+
+error:
+        hashmap_remove(s->devices, ULONG_TO_PTR((unsigned long)sd->dev));
+        free(sd->node);
+        free(sd);
+        return r;
+}
+
+void session_device_free(SessionDevice *sd) {
+        assert(sd);
+
+        session_device_stop(sd);
+        session_device_notify(sd, SESSION_DEVICE_RELEASE);
+        close_nointr_nofail(sd->fd);
+
+        LIST_REMOVE(SessionDevice, sd_by_device, sd->device->session_devices, sd);
+
+        hashmap_remove(sd->session->devices, ULONG_TO_PTR((unsigned long)sd->dev));
+
+        free(sd->node);
+        free(sd);
+}
+
+void session_device_complete_pause(SessionDevice *sd) {
+        if (!sd->active)
+                return;
+
+        session_device_stop(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);
+                }
+        }
+}
+
+void session_device_pause_all(Session *s) {
+        SessionDevice *sd;
+        Iterator i;
+
+        assert(s);
+
+        HASHMAP_FOREACH(sd, s->devices, i) {
+                if (sd->active) {
+                        session_device_stop(sd);
+                        session_device_notify(sd, SESSION_DEVICE_PAUSE);
+                }
+        }
+}
diff --git a/src/login/logind-session-device.h b/src/login/logind-session-device.h
new file mode 100644
index 0000000..511fce0
--- /dev/null
+++ b/src/login/logind-session-device.h
@@ -0,0 +1,59 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  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/>.
+***/
+
+typedef enum DeviceType DeviceType;
+typedef struct SessionDevice SessionDevice;
+
+#include "list.h"
+#include "util.h"
+#include "logind.h"
+#include "logind-device.h"
+#include "logind-seat.h"
+#include "logind-session.h"
+
+enum DeviceType {
+        DEVICE_TYPE_UNKNOWN,
+        DEVICE_TYPE_FBDEV,
+        DEVICE_TYPE_DRM,
+        DEVICE_TYPE_EVDEV,
+};
+
+struct SessionDevice {
+        Session *session;
+        Device *device;
+
+        dev_t dev;
+        char *node;
+        int fd;
+        bool active;
+        DeviceType type;
+
+        LIST_FIELDS(struct SessionDevice, sd_by_device);
+};
+
+int session_device_new(Session *s, dev_t dev, SessionDevice **out);
+void session_device_free(SessionDevice *sd);
+void session_device_complete_pause(SessionDevice *sd);
+
+void session_device_resume_all(Session *s);
+void session_device_pause_all(Session *s);
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
index f856127..fcc1901 100644
--- a/src/login/logind-session.c
+++ b/src/login/logind-session.c
@@ -53,9 +53,17 @@ Session* session_new(Manager *m, const char *id) {
                 return NULL;
         }
 
+        s->devices = hashmap_new(trivial_hash_func, trivial_compare_func);
+        if (!s->devices) {
+                free(s->state_file);
+                free(s);
+                return NULL;
+        }
+
         s->id = path_get_file_name(s->state_file);
 
         if (hashmap_put(m->sessions, s->id, s) < 0) {
+                hashmap_free(s->devices);
                 free(s->state_file);
                 free(s);
                 return NULL;
@@ -68,6 +76,8 @@ Session* session_new(Manager *m, const char *id) {
 }
 
 void session_free(Session *s) {
+        SessionDevice *sd;
+
         assert(s);
 
         if (s->in_gc_queue)
@@ -75,6 +85,11 @@ void session_free(Session *s) {
 
         session_drop_controller(s);
 
+        while ((sd = hashmap_first(s->devices)))
+                session_device_free(sd);
+
+        hashmap_free(s->devices);
+
         if (s->user) {
                 LIST_REMOVE(Session, sessions_by_user, s->user->sessions, s);
 
@@ -612,6 +627,7 @@ int session_stop(Session *s) {
 
 int session_finalize(Session *s) {
         int r = 0;
+        SessionDevice *sd;
 
         assert(s);
 
@@ -627,6 +643,10 @@ int session_finalize(Session *s) {
                            "MESSAGE=Removed session %s.", s->id,
                            NULL);
 
+        /* Kill session devices */
+        while ((sd = hashmap_first(s->devices)))
+                session_device_free(sd);
+
         /* Remove X11 symlink */
         session_unlink_x11_socket(s);
 
@@ -950,6 +970,8 @@ int session_set_controller(Session *s, const char *sender, bool force) {
 }
 
 void session_drop_controller(Session *s) {
+        SessionDevice *sd;
+
         assert(s);
 
         if (!s->controller)
@@ -958,6 +980,11 @@ void session_drop_controller(Session *s) {
         manager_drop_busname(s->manager, s->controller);
         free(s->controller);
         s->controller = NULL;
+
+        /* Drop all devices as they're now unused. Do that after the controller
+         * is released to avoid sending out useles dbus signals. */
+        while ((sd = hashmap_first(s->devices)))
+                session_device_free(sd);
 }
 
 static const char* const session_state_table[_SESSION_STATE_MAX] = {
diff --git a/src/login/logind-session.h b/src/login/logind-session.h
index 839fb23..f175a89 100644
--- a/src/login/logind-session.h
+++ b/src/login/logind-session.h
@@ -28,6 +28,7 @@ typedef enum KillWho KillWho;
 #include "util.h"
 #include "logind.h"
 #include "logind-seat.h"
+#include "logind-session-device.h"
 #include "logind-user.h"
 #include "login-shared.h"
 
@@ -107,6 +108,7 @@ struct Session {
         DBusMessage *create_message;
 
         char *controller;
+        Hashmap *devices;
 
         LIST_FIELDS(Session, sessions_by_user);
         LIST_FIELDS(Session, sessions_by_seat);
diff --git a/src/shared/missing.h b/src/shared/missing.h
index d1ca135..6c038d9 100644
--- a/src/shared/missing.h
+++ b/src/shared/missing.h
@@ -29,6 +29,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <linux/oom.h>
+#include <linux/input.h>
 
 #ifdef HAVE_AUDIT
 #include <libaudit.h>
@@ -310,3 +311,15 @@ static inline int name_to_handle_at(int fd, const char *name, struct file_handle
 #ifndef SO_REUSEPORT
 #define SO_REUSEPORT 15
 #endif
+
+#ifndef EVIOCREVOKE
+#define EVIOCREVOKE _IOW('E', 0x91, int)
+#endif
+
+#ifndef DRM_IOCTL_SET_MASTER
+#define DRM_IOCTL_SET_MASTER _IO('d', 0x1e)
+#endif
+
+#ifndef DRM_IOCTL_DROP_MASTER
+#define DRM_IOCTL_DROP_MASTER _IO('d', 0x1f)
+#endif

commit 360e09ea9ad3a8e84e1729ebd2967ab8f7348170
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Sep 17 17:11:46 2013 -0500

    Update TODO

diff --git a/TODO b/TODO
index b5a93b2..d5b32ac 100644
--- a/TODO
+++ b/TODO
@@ -58,6 +58,8 @@ CGroup Rework Completion:
 
 Features:
 
+* libdsystemd-bus should expose utf8 validation calls
+
 * When using "systemd status" on a slice unit also show all messages
   matching _SYSTEMD_SLICE= not just _SYSTEMD_UNIT=
 



More information about the systemd-commits mailing list