1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2013 David Herrmann
9 #include <linux/input.h>
11 #include <sys/ioctl.h>
12 #include <sys/types.h>
16 #include "alloc-util.h"
19 #include "logind-session-device.h"
21 #include "parse-util.h"
22 #include "sd-daemon.h"
25 enum SessionDeviceNotifications
{
26 SESSION_DEVICE_RESUME
,
27 SESSION_DEVICE_TRY_PAUSE
,
29 SESSION_DEVICE_RELEASE
,
32 static int session_device_notify(SessionDevice
*sd
, enum SessionDeviceNotifications type
) {
33 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
34 _cleanup_free_
char *path
= NULL
;
36 uint32_t major
, minor
;
41 major
= major(sd
->dev
);
42 minor
= minor(sd
->dev
);
44 if (!sd
->session
->controller
)
47 path
= session_bus_path(sd
->session
);
51 r
= sd_bus_message_new_signal(
52 sd
->session
->manager
->bus
,
54 "org.freedesktop.login1.Session",
55 (type
== SESSION_DEVICE_RESUME
) ? "ResumeDevice" : "PauseDevice");
59 r
= sd_bus_message_set_destination(m
, sd
->session
->controller
);
65 case SESSION_DEVICE_RESUME
:
66 r
= sd_bus_message_append(m
, "uuh", major
, minor
, sd
->fd
);
71 case SESSION_DEVICE_TRY_PAUSE
:
75 case SESSION_DEVICE_PAUSE
:
79 case SESSION_DEVICE_RELEASE
:
88 r
= sd_bus_message_append(m
, "uus", major
, minor
, t
);
93 return sd_bus_send(sd
->session
->manager
->bus
, m
, NULL
);
96 static void sd_eviocrevoke(int fd
) {
97 static bool warned
= false;
101 if (ioctl(fd
, EVIOCREVOKE
, NULL
) < 0) {
103 if (errno
== EINVAL
&& !warned
) {
104 log_warning_errno(errno
, "Kernel does not support evdev-revocation: %m");
110 static int sd_drmsetmaster(int fd
) {
113 if (ioctl(fd
, DRM_IOCTL_SET_MASTER
, 0) < 0)
119 static int sd_drmdropmaster(int fd
) {
122 if (ioctl(fd
, DRM_IOCTL_DROP_MASTER
, 0) < 0)
128 static int session_device_open(SessionDevice
*sd
, bool active
) {
132 assert(sd
->type
!= DEVICE_TYPE_UNKNOWN
);
135 /* open device and try to get an udev_device from it */
136 fd
= open(sd
->node
, O_RDWR
|O_CLOEXEC
|O_NOCTTY
|O_NONBLOCK
);
142 case DEVICE_TYPE_DRM
:
144 /* Weird legacy DRM semantics might return an error even though we're master. No way to detect
145 * that so fail at all times and let caller retry in inactive state. */
146 r
= sd_drmsetmaster(fd
);
152 /* DRM-Master is granted to the first user who opens a device automatically (ughh,
153 * racy!). Hence, we just drop DRM-Master in case we were the first. */
154 (void) sd_drmdropmaster(fd
);
157 case DEVICE_TYPE_EVDEV
:
162 case DEVICE_TYPE_UNKNOWN
:
164 /* fallback for devices wihout synchronizations */
171 static int session_device_start(SessionDevice
*sd
) {
175 assert(session_is_active(sd
->session
));
182 case DEVICE_TYPE_DRM
:
184 log_error("Failed to re-activate DRM fd, as the fd was lost (maybe logind restart went wrong?)");
188 /* Device is kept open. Simply call drmSetMaster() and hope there is no-one else. In case it fails, we
189 * keep the device paused. Maybe at some point we have a drmStealMaster(). */
190 r
= sd_drmsetmaster(sd
->fd
);
195 case DEVICE_TYPE_EVDEV
:
196 /* Evdev devices are revoked while inactive. Reopen it and we are fine. */
197 r
= session_device_open(sd
, true);
201 /* For evdev devices, the file descriptor might be left uninitialized. This might happen while resuming
202 * into a session and logind has been restarted right before. */
207 case DEVICE_TYPE_UNKNOWN
:
209 /* fallback for devices without synchronizations */
217 static void session_device_stop(SessionDevice
*sd
) {
225 case DEVICE_TYPE_DRM
:
227 log_error("Failed to de-activate DRM fd, as the fd was lost (maybe logind restart went wrong?)");
231 /* On DRM devices we simply drop DRM-Master but keep it open.
232 * This allows the user to keep resources allocated. The
233 * CAP_SYS_ADMIN restriction to DRM-Master prevents users from
234 * circumventing this. */
235 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
);
246 case DEVICE_TYPE_UNKNOWN
:
248 /* fallback for devices without synchronization */
255 static DeviceType
detect_device_type(struct udev_device
*dev
) {
256 const char *sysname
, *subsystem
;
259 sysname
= udev_device_get_sysname(dev
);
260 subsystem
= udev_device_get_subsystem(dev
);
261 type
= DEVICE_TYPE_UNKNOWN
;
263 if (streq_ptr(subsystem
, "drm")) {
264 if (startswith(sysname
, "card"))
265 type
= DEVICE_TYPE_DRM
;
266 } else if (streq_ptr(subsystem
, "input")) {
267 if (startswith(sysname
, "event"))
268 type
= DEVICE_TYPE_EVDEV
;
274 static int session_device_verify(SessionDevice
*sd
) {
275 struct udev_device
*dev
, *p
= NULL
;
276 const char *sp
, *node
;
279 dev
= udev_device_new_from_devnum(sd
->session
->manager
->udev
, 'c', sd
->dev
);
283 sp
= udev_device_get_syspath(dev
);
284 node
= udev_device_get_devnode(dev
);
290 /* detect device type so we can find the correct sysfs parent */
291 sd
->type
= detect_device_type(dev
);
292 if (sd
->type
== DEVICE_TYPE_UNKNOWN
) {
295 } else if (sd
->type
== DEVICE_TYPE_EVDEV
) {
296 /* for evdev devices we need the parent node as device */
298 dev
= udev_device_get_parent_with_subsystem_devtype(p
, "input", NULL
);
303 sp
= udev_device_get_syspath(dev
);
304 } else if (sd
->type
!= DEVICE_TYPE_DRM
) {
305 /* Prevent opening unsupported devices. Especially devices of
306 * subsystem "input" must be opened via the evdev node as
307 * we require EVIOCREVOKE. */
312 /* search for an existing seat device and return it if available */
313 sd
->device
= hashmap_get(sd
->session
->manager
->devices
, sp
);
315 /* The caller might have gotten the udev event before we were
316 * able to process it. Hence, fake the "add" event and let the
317 * logind-manager handle the new device. */
318 r
= manager_process_seat_device(sd
->session
->manager
, dev
);
322 /* if it's still not available, then the device is invalid */
323 sd
->device
= hashmap_get(sd
->session
->manager
->devices
, sp
);
330 if (sd
->device
->seat
!= sd
->session
->seat
) {
335 sd
->node
= strdup(node
);
343 udev_device_unref(p
? : dev
);
347 int session_device_new(Session
*s
, dev_t dev
, bool open_device
, SessionDevice
**out
) {
357 sd
= new0(SessionDevice
, 1);
364 sd
->type
= DEVICE_TYPE_UNKNOWN
;
366 r
= session_device_verify(sd
);
370 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);
393 LIST_PREPEND(sd_by_device
, sd
->device
->session_devices
, sd
);
399 hashmap_remove(s
->devices
, &sd
->dev
);
405 void session_device_free(SessionDevice
*sd
) {
408 /* Make sure to remove the pushed fd. */
410 _cleanup_free_
char *m
= NULL
;
414 /* Session ID does not contain separators. */
415 id
= sd
->session
->id
;
416 assert(*(id
+ strcspn(id
, "-\n")) == '\0');
418 r
= asprintf(&m
, "FDSTOREREMOVE=1\n"
419 "FDNAME=session-%s-device-%u-%u\n",
420 id
, major(sd
->dev
), minor(sd
->dev
));
422 (void) sd_notify(false, m
);
425 session_device_stop(sd
);
426 session_device_notify(sd
, SESSION_DEVICE_RELEASE
);
429 LIST_REMOVE(sd_by_device
, sd
->device
->session_devices
, sd
);
431 hashmap_remove(sd
->session
->devices
, &sd
->dev
);
437 void session_device_complete_pause(SessionDevice
*sd
) {
444 session_device_stop(sd
);
446 /* if not all devices are paused, wait for further completion events */
447 HASHMAP_FOREACH(iter
, sd
->session
->devices
, i
)
451 /* complete any pending session switch */
452 seat_complete_switch(sd
->session
->seat
);
455 void session_device_resume_all(Session
*s
) {
461 HASHMAP_FOREACH(sd
, s
->devices
, i
) {
465 if (session_device_start(sd
) < 0)
467 if (session_device_save(sd
) < 0)
470 session_device_notify(sd
, SESSION_DEVICE_RESUME
);
474 void session_device_pause_all(Session
*s
) {
480 HASHMAP_FOREACH(sd
, s
->devices
, i
) {
484 session_device_stop(sd
);
485 session_device_notify(sd
, SESSION_DEVICE_PAUSE
);
489 unsigned int session_device_try_pause_all(Session
*s
) {
490 unsigned num_pending
= 0;
496 HASHMAP_FOREACH(sd
, s
->devices
, i
) {
500 session_device_notify(sd
, SESSION_DEVICE_TRY_PAUSE
);
507 int session_device_save(SessionDevice
*sd
) {
508 _cleanup_free_
char *m
= NULL
;
514 /* Store device fd in PID1. It will send it back to us on restart so revocation will continue to work. To make
515 * things simple, send fds for all type of devices even if they don't support the revocation mechanism so we
516 * don't have to handle them differently later.
518 * Note: for device supporting revocation, PID1 will drop a stored fd automatically if the corresponding device
524 /* Session ID does not contain separators. */
525 id
= sd
->session
->id
;
526 assert(*(id
+ strcspn(id
, "-\n")) == '\0');
528 r
= asprintf(&m
, "FDSTORE=1\n"
529 "FDNAME=session-%s-device-%u-%u\n",
530 id
, major(sd
->dev
), minor(sd
->dev
));
534 r
= sd_pid_notify_with_fds(0, false, m
, &sd
->fd
, 1);
538 sd
->pushed_fd
= true;
542 void session_device_attach_fd(SessionDevice
*sd
, int fd
, bool active
) {
549 sd
->pushed_fd
= true;