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/>.
23 #include <linux/input.h>
25 #include <sys/ioctl.h>
26 #include <sys/types.h>
30 #include "alloc-util.h"
33 #include "logind-session-device.h"
37 enum SessionDeviceNotifications {
38 SESSION_DEVICE_RESUME,
39 SESSION_DEVICE_TRY_PAUSE,
41 SESSION_DEVICE_RELEASE,
44 static int session_device_notify(SessionDevice *sd, enum SessionDeviceNotifications type) {
45 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
46 _cleanup_free_ char *path = NULL;
48 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 r = sd_bus_message_new_signal(
64 sd->session->manager->bus,
66 "org.freedesktop.login1.Session",
67 (type == SESSION_DEVICE_RESUME) ? "ResumeDevice" : "PauseDevice");
71 r = sd_bus_message_set_destination(m, sd->session->controller);
76 case SESSION_DEVICE_RESUME:
77 r = sd_bus_message_append(m, "uuh", major, minor, sd->fd);
81 case SESSION_DEVICE_TRY_PAUSE:
84 case SESSION_DEVICE_PAUSE:
87 case SESSION_DEVICE_RELEASE:
95 r = sd_bus_message_append(m, "uus", major, minor, t);
100 return sd_bus_send(sd->session->manager->bus, m, NULL);
103 static int sd_eviocrevoke(int fd) {
109 r = ioctl(fd, EVIOCREVOKE, NULL);
112 if (r == -EINVAL && !warned) {
114 log_warning("kernel does not support evdev-revocation");
121 static int sd_drmsetmaster(int fd) {
126 r = ioctl(fd, DRM_IOCTL_SET_MASTER, 0);
133 static int sd_drmdropmaster(int fd) {
138 r = ioctl(fd, DRM_IOCTL_DROP_MASTER, 0);
145 static int session_device_open(SessionDevice *sd, bool active) {
148 assert(sd->type != DEVICE_TYPE_UNKNOWN);
150 /* open device and try to get an udev_device from it */
151 fd = open(sd->node, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
156 case DEVICE_TYPE_DRM:
158 /* Weird legacy DRM semantics might return an error
159 * even though we're master. No way to detect that so
160 * fail at all times and let caller retry in inactive
162 r = sd_drmsetmaster(fd);
168 /* DRM-Master is granted to the first user who opens a
169 * device automatically (ughh, racy!). Hence, we just
170 * drop DRM-Master in case we were the first. */
171 sd_drmdropmaster(fd);
174 case DEVICE_TYPE_EVDEV:
178 case DEVICE_TYPE_UNKNOWN:
180 /* fallback for devices wihout synchronizations */
187 static int session_device_start(SessionDevice *sd) {
191 assert(session_is_active(sd->session));
197 case DEVICE_TYPE_DRM:
198 /* Device is kept open. Simply call drmSetMaster() and hope
199 * there is no-one else. In case it fails, we keep the device
200 * paused. Maybe at some point we have a drmStealMaster(). */
201 r = sd_drmsetmaster(sd->fd);
205 case DEVICE_TYPE_EVDEV:
206 /* Evdev devices are revoked while inactive. Reopen it and we
208 r = session_device_open(sd, true);
211 close_nointr(sd->fd);
214 case DEVICE_TYPE_UNKNOWN:
216 /* fallback for devices wihout synchronizations */
224 static void session_device_stop(SessionDevice *sd) {
231 case DEVICE_TYPE_DRM:
232 /* On DRM devices we simply drop DRM-Master but keep it open.
233 * This allows the user to keep resources allocated. The
234 * CAP_SYS_ADMIN restriction to DRM-Master prevents users from
235 * circumventing this. */
236 sd_drmdropmaster(sd->fd);
238 case DEVICE_TYPE_EVDEV:
239 /* Revoke access on evdev file-descriptors during deactivation.
240 * This will basically prevent any operations on the fd and
241 * cannot be undone. Good side is: it needs no CAP_SYS_ADMIN
242 * protection this way. */
243 sd_eviocrevoke(sd->fd);
245 case DEVICE_TYPE_UNKNOWN:
247 /* fallback for devices without synchronization */
254 static DeviceType detect_device_type(struct udev_device *dev) {
255 const char *sysname, *subsystem;
258 sysname = udev_device_get_sysname(dev);
259 subsystem = udev_device_get_subsystem(dev);
260 type = DEVICE_TYPE_UNKNOWN;
262 if (streq_ptr(subsystem, "drm")) {
263 if (startswith(sysname, "card"))
264 type = DEVICE_TYPE_DRM;
265 } else if (streq_ptr(subsystem, "input")) {
266 if (startswith(sysname, "event"))
267 type = DEVICE_TYPE_EVDEV;
273 static int session_device_verify(SessionDevice *sd) {
274 struct udev_device *dev, *p = NULL;
275 const char *sp, *node;
278 dev = udev_device_new_from_devnum(sd->session->manager->udev, 'c', sd->dev);
282 sp = udev_device_get_syspath(dev);
283 node = udev_device_get_devnode(dev);
289 /* detect device type so we can find the correct sysfs parent */
290 sd->type = detect_device_type(dev);
291 if (sd->type == DEVICE_TYPE_UNKNOWN) {
294 } else if (sd->type == DEVICE_TYPE_EVDEV) {
295 /* for evdev devices we need the parent node as device */
297 dev = udev_device_get_parent_with_subsystem_devtype(p, "input", NULL);
302 sp = udev_device_get_syspath(dev);
303 } else if (sd->type != DEVICE_TYPE_DRM) {
304 /* Prevent opening unsupported devices. Especially devices of
305 * subsystem "input" must be opened via the evdev node as
306 * we require EVIOCREVOKE. */
311 /* search for an existing seat device and return it if available */
312 sd->device = hashmap_get(sd->session->manager->devices, sp);
314 /* The caller might have gotten the udev event before we were
315 * able to process it. Hence, fake the "add" event and let the
316 * logind-manager handle the new device. */
317 r = manager_process_seat_device(sd->session->manager, dev);
321 /* if it's still not available, then the device is invalid */
322 sd->device = hashmap_get(sd->session->manager->devices, sp);
329 if (sd->device->seat != sd->session->seat) {
334 sd->node = strdup(node);
342 udev_device_unref(p ? : dev);
346 int session_device_new(Session *s, dev_t dev, SessionDevice **out) {
356 sd = new0(SessionDevice, 1);
363 sd->type = DEVICE_TYPE_UNKNOWN;
365 r = session_device_verify(sd);
369 r = hashmap_put(s->devices, &sd->dev, sd);
375 /* Open the device for the first time. We need a valid fd to pass back
376 * to the caller. If the session is not active, this _might_ immediately
377 * revoke access and thus invalidate the fd. But this is still needed
378 * to pass a valid fd back. */
379 sd->active = session_is_active(s);
380 r = session_device_open(sd, sd->active);
382 /* EINVAL _may_ mean a master is active; retry inactive */
383 if (sd->active && r == -EINVAL) {
385 r = session_device_open(sd, false);
392 LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
398 hashmap_remove(s->devices, &sd->dev);
404 void session_device_free(SessionDevice *sd) {
407 session_device_stop(sd);
408 session_device_notify(sd, SESSION_DEVICE_RELEASE);
409 close_nointr(sd->fd);
411 LIST_REMOVE(sd_by_device, sd->device->session_devices, sd);
413 hashmap_remove(sd->session->devices, &sd->dev);
419 void session_device_complete_pause(SessionDevice *sd) {
426 session_device_stop(sd);
428 /* if not all devices are paused, wait for further completion events */
429 HASHMAP_FOREACH(iter, sd->session->devices, i)
433 /* complete any pending session switch */
434 seat_complete_switch(sd->session->seat);
437 void session_device_resume_all(Session *s) {
444 HASHMAP_FOREACH(sd, s->devices, i) {
446 r = session_device_start(sd);
448 session_device_notify(sd, SESSION_DEVICE_RESUME);
453 void session_device_pause_all(Session *s) {
459 HASHMAP_FOREACH(sd, s->devices, i) {
461 session_device_stop(sd);
462 session_device_notify(sd, SESSION_DEVICE_PAUSE);
467 unsigned int session_device_try_pause_all(Session *s) {
470 unsigned int num_pending = 0;
474 HASHMAP_FOREACH(sd, s->devices, i) {
476 session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE);