1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2013 David Herrmann
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
25 #include <linux/input.h>
26 #include <linux/ioctl.h>
28 #include <sys/ioctl.h>
30 #include <sys/types.h>
33 #include "dbus-common.h"
34 #include "logind-session-device.h"
38 enum SessionDeviceNotifications {
39 SESSION_DEVICE_RESUME,
40 SESSION_DEVICE_TRY_PAUSE,
42 SESSION_DEVICE_RELEASE,
45 static void session_device_notify(SessionDevice *sd, enum SessionDeviceNotifications type) {
46 _cleanup_dbus_message_unref_ DBusMessage *m = NULL;
47 _cleanup_free_ char *path = NULL;
49 uint32_t major, minor;
53 major = major(sd->dev);
54 minor = minor(sd->dev);
56 if (!sd->session->controller)
59 path = session_bus_path(sd->session);
63 m = dbus_message_new_signal(path,
64 "org.freedesktop.login1.Session",
65 (type == SESSION_DEVICE_RESUME) ? "ResumeDevice" : "PauseDevice");
69 if (!dbus_message_set_destination(m, sd->session->controller))
73 case SESSION_DEVICE_RESUME:
74 if (!dbus_message_append_args(m,
75 DBUS_TYPE_UINT32, &major,
76 DBUS_TYPE_UINT32, &minor,
77 DBUS_TYPE_UNIX_FD, &sd->fd,
81 case SESSION_DEVICE_TRY_PAUSE:
84 case SESSION_DEVICE_PAUSE:
87 case SESSION_DEVICE_RELEASE:
94 if (t && !dbus_message_append_args(m,
95 DBUS_TYPE_UINT32, &major,
96 DBUS_TYPE_UINT32, &minor,
101 dbus_connection_send(sd->session->manager->bus, m, NULL);
104 static int sd_eviocrevoke(int fd) {
110 r = ioctl(fd, EVIOCREVOKE, 1);
113 if (r == -EINVAL && !warned) {
115 log_warning("kernel does not support evdev-revocation");
122 static int sd_drmsetmaster(int fd) {
127 r = ioctl(fd, DRM_IOCTL_SET_MASTER, 0);
134 static int sd_drmdropmaster(int fd) {
139 r = ioctl(fd, DRM_IOCTL_DROP_MASTER, 0);
146 static int session_device_open(SessionDevice *sd, bool active) {
149 assert(sd->type != DEVICE_TYPE_UNKNOWN);
151 /* open device and try to get an udev_device from it */
152 fd = open(sd->node, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
157 case DEVICE_TYPE_DRM:
161 /* DRM-Master is granted to the first user who opens a
162 * device automatically (ughh, racy!). Hence, we just
163 * drop DRM-Master in case we were the first. */
164 sd_drmdropmaster(fd);
167 case DEVICE_TYPE_EVDEV:
171 case DEVICE_TYPE_FBDEV:
172 case DEVICE_TYPE_UNKNOWN:
174 /* fallback for devices wihout synchronizations */
181 static int session_device_start(SessionDevice *sd) {
185 assert(session_is_active(sd->session));
191 case DEVICE_TYPE_DRM:
192 /* Device is kept open. Simply call drmSetMaster() and hope
193 * there is no-one else. In case it fails, we keep the device
194 * paused. Maybe at some point we have a drmStealMaster(). */
195 r = sd_drmsetmaster(sd->fd);
199 case DEVICE_TYPE_EVDEV:
200 /* Evdev devices are revoked while inactive. Reopen it and we
202 r = session_device_open(sd, true);
205 close_nointr_nofail(sd->fd);
208 case DEVICE_TYPE_FBDEV:
209 /* fbdev devices have no way to synchronize access. Moreover,
210 * they mostly operate through mmaps() without any pageflips
211 * and modesetting, so there is no way for us to prevent access
212 * but tear down mmaps.
213 * That would be quite expensive to do on a per-fd context. So
214 * ignore legcy fbdev and let its users feel the pain they asked
215 * for when deciding for fbdev. */
216 case DEVICE_TYPE_UNKNOWN:
218 /* fallback for devices wihout synchronizations */
226 static void session_device_stop(SessionDevice *sd) {
233 case DEVICE_TYPE_DRM:
234 /* On DRM devices we simply drop DRM-Master but keep it open.
235 * This allows the user to keep resources allocated. The
236 * CAP_SYS_ADMIN restriction to DRM-Master prevents users from
237 * circumventing this. */
238 sd_drmdropmaster(sd->fd);
240 case DEVICE_TYPE_EVDEV:
241 /* Revoke access on evdev file-descriptors during deactivation.
242 * This will basically prevent any operations on the fd and
243 * cannot be undone. Good side is: it needs no CAP_SYS_ADMIN
244 * protection this way. */
245 sd_eviocrevoke(sd->fd);
247 case DEVICE_TYPE_FBDEV:
248 case DEVICE_TYPE_UNKNOWN:
250 /* fallback for devices without synchronization */
257 static DeviceType detect_device_type(struct udev_device *dev) {
258 const char *sysname, *subsystem;
261 sysname = udev_device_get_sysname(dev);
262 subsystem = udev_device_get_subsystem(dev);
263 type = DEVICE_TYPE_UNKNOWN;
265 if (streq_ptr(subsystem, "graphics")) {
266 if (!streq(sysname, "fbcon") && startswith(sysname, "fb"))
267 type = DEVICE_TYPE_FBDEV;
268 } else if (streq_ptr(subsystem, "drm")) {
269 if (startswith(sysname, "card"))
270 type = DEVICE_TYPE_DRM;
271 } else if (streq_ptr(subsystem, "input")) {
272 if (startswith(sysname, "event"))
273 type = DEVICE_TYPE_EVDEV;
279 static int session_device_verify(SessionDevice *sd) {
280 struct udev_device *dev, *p = NULL;
281 const char *sp, *node;
284 dev = udev_device_new_from_devnum(sd->session->manager->udev, 'c', sd->dev);
288 sp = udev_device_get_syspath(dev);
289 node = udev_device_get_devnode(dev);
295 /* detect device type so we can find the correct sysfs parent */
296 sd->type = detect_device_type(dev);
297 if (sd->type == DEVICE_TYPE_UNKNOWN) {
300 } else if (sd->type == DEVICE_TYPE_EVDEV) {
301 /* for evdev devices we need the parent node as device */
303 dev = udev_device_get_parent_with_subsystem_devtype(p, "input", NULL);
308 sp = udev_device_get_syspath(dev);
309 } else if (sd->type != DEVICE_TYPE_FBDEV &&
310 sd->type != DEVICE_TYPE_DRM) {
311 /* Prevent opening unsupported devices. Especially devices of
312 * subsystem "input" must be opened via the evdev node as
313 * we require EVIOCREVOKE. */
318 /* search for an existing seat device and return it if available */
319 sd->device = hashmap_get(sd->session->manager->devices, sp);
321 /* The caller might have gotten the udev event before we were
322 * able to process it. Hence, fake the "add" event and let the
323 * logind-manager handle the new device. */
324 r = manager_process_seat_device(sd->session->manager, dev);
328 /* if it's still not available, then the device is invalid */
329 sd->device = hashmap_get(sd->session->manager->devices, sp);
336 if (sd->device->seat != sd->session->seat) {
341 sd->node = strdup(node);
349 udev_device_unref(p ? : dev);
353 int session_device_new(Session *s, dev_t dev, SessionDevice **out) {
363 sd = new0(SessionDevice, 1);
370 sd->type = DEVICE_TYPE_UNKNOWN;
372 r = session_device_verify(sd);
376 r = hashmap_put(s->devices, &sd->dev, sd);
382 /* Open the device for the first time. We need a valid fd to pass back
383 * to the caller. If the session is not active, this _might_ immediately
384 * revoke access and thus invalidate the fd. But this is still needed
385 * to pass a valid fd back. */
386 sd->active = session_is_active(s);
387 sd->fd = session_device_open(sd, sd->active);
391 LIST_PREPEND(SessionDevice, sd_by_device, sd->device->session_devices, sd);
397 hashmap_remove(s->devices, &sd->dev);
403 void session_device_free(SessionDevice *sd) {
406 session_device_stop(sd);
407 session_device_notify(sd, SESSION_DEVICE_RELEASE);
408 close_nointr_nofail(sd->fd);
410 LIST_REMOVE(SessionDevice, sd_by_device, sd->device->session_devices, sd);
412 hashmap_remove(sd->session->devices, &sd->dev);
418 void session_device_complete_pause(SessionDevice *sd) {
425 session_device_stop(sd);
427 /* if not all devices are paused, wait for further completion events */
428 HASHMAP_FOREACH(iter, sd->session->devices, i)
432 /* complete any pending session switch */
433 seat_complete_switch(sd->session->seat);
436 void session_device_resume_all(Session *s) {
443 HASHMAP_FOREACH(sd, s->devices, i) {
445 r = session_device_start(sd);
447 session_device_notify(sd, SESSION_DEVICE_RESUME);
452 void session_device_pause_all(Session *s) {
458 HASHMAP_FOREACH(sd, s->devices, i) {
460 session_device_stop(sd);
461 session_device_notify(sd, SESSION_DEVICE_PAUSE);
466 unsigned int session_device_try_pause_all(Session *s) {
469 unsigned int num_pending = 0;
473 HASHMAP_FOREACH(sd, s->devices, i) {
475 session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE);