]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-session-device.c
tree-wide: use device_in_subsystem() and device_is_devtype()
[thirdparty/systemd.git] / src / login / logind-session-device.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <fcntl.h>
4 #include <string.h>
5 #include <sys/ioctl.h>
6 #include <sys/types.h>
7
8 #include "sd-device.h"
9 #include "sd-daemon.h"
10
11 #include "alloc-util.h"
12 #include "bus-util.h"
13 #include "daemon-util.h"
14 #include "device-util.h"
15 #include "fd-util.h"
16 #include "logind-session-dbus.h"
17 #include "logind-session-device.h"
18 #include "missing_drm.h"
19 #include "missing_input.h"
20 #include "parse-util.h"
21
22 enum SessionDeviceNotifications {
23 SESSION_DEVICE_RESUME,
24 SESSION_DEVICE_TRY_PAUSE,
25 SESSION_DEVICE_PAUSE,
26 SESSION_DEVICE_RELEASE,
27 };
28
29 static int session_device_notify(SessionDevice *sd, enum SessionDeviceNotifications type) {
30 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
31 _cleanup_free_ char *path = NULL;
32 const char *t = NULL;
33 uint32_t major, minor;
34 int r;
35
36 assert(sd);
37
38 major = major(sd->dev);
39 minor = minor(sd->dev);
40
41 if (!sd->session->controller)
42 return 0;
43
44 path = session_bus_path(sd->session);
45 if (!path)
46 return -ENOMEM;
47
48 r = sd_bus_message_new_signal(
49 sd->session->manager->bus,
50 &m, path,
51 "org.freedesktop.login1.Session",
52 type == SESSION_DEVICE_RESUME ? "ResumeDevice" : "PauseDevice");
53 if (!m)
54 return r;
55
56 r = sd_bus_message_set_destination(m, sd->session->controller);
57 if (r < 0)
58 return r;
59
60 switch (type) {
61
62 case SESSION_DEVICE_RESUME:
63 r = sd_bus_message_append(m, "uuh", major, minor, sd->fd);
64 if (r < 0)
65 return r;
66 break;
67
68 case SESSION_DEVICE_TRY_PAUSE:
69 t = "pause";
70 break;
71
72 case SESSION_DEVICE_PAUSE:
73 t = "force";
74 break;
75
76 case SESSION_DEVICE_RELEASE:
77 t = "gone";
78 break;
79
80 default:
81 return -EINVAL;
82 }
83
84 if (t) {
85 r = sd_bus_message_append(m, "uus", major, minor, t);
86 if (r < 0)
87 return r;
88 }
89
90 return sd_bus_send(sd->session->manager->bus, m, NULL);
91 }
92
93 static void sd_eviocrevoke(int fd) {
94 static bool warned = false;
95
96 assert(fd >= 0);
97
98 if (ioctl(fd, EVIOCREVOKE, NULL) < 0) {
99
100 if (errno == EINVAL && !warned) {
101 log_warning_errno(errno, "Kernel does not support evdev-revocation: %m");
102 warned = true;
103 }
104 }
105 }
106
107 static int sd_drmsetmaster(int fd) {
108 assert(fd >= 0);
109 return RET_NERRNO(ioctl(fd, DRM_IOCTL_SET_MASTER, 0));
110 }
111
112 static int sd_drmdropmaster(int fd) {
113 assert(fd >= 0);
114 return RET_NERRNO(ioctl(fd, DRM_IOCTL_DROP_MASTER, 0));
115 }
116
117 static int session_device_open(SessionDevice *sd, bool active) {
118 int fd, r;
119
120 assert(sd);
121 assert(sd->type != DEVICE_TYPE_UNKNOWN);
122 assert(sd->node);
123
124 /* open device and try to get a udev_device from it */
125 fd = open(sd->node, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
126 if (fd < 0)
127 return -errno;
128
129 switch (sd->type) {
130
131 case DEVICE_TYPE_DRM:
132 if (active) {
133 /* Weird legacy DRM semantics might return an error even though we're master. No way to detect
134 * that so fail at all times and let caller retry in inactive state. */
135 r = sd_drmsetmaster(fd);
136 if (r < 0) {
137 (void) close_nointr(fd);
138 return r;
139 }
140 } else
141 /* DRM-Master is granted to the first user who opens a device automatically (ughh,
142 * racy!). Hence, we just drop DRM-Master in case we were the first. */
143 (void) sd_drmdropmaster(fd);
144 break;
145
146 case DEVICE_TYPE_EVDEV:
147 if (!active)
148 sd_eviocrevoke(fd);
149 break;
150
151 case DEVICE_TYPE_UNKNOWN:
152 default:
153 /* fallback for devices without synchronizations */
154 break;
155 }
156
157 return fd;
158 }
159
160 static int session_device_start(SessionDevice *sd) {
161 int r;
162
163 assert(sd);
164 assert(session_is_active(sd->session));
165
166 if (sd->active)
167 return 0;
168
169 switch (sd->type) {
170
171 case DEVICE_TYPE_DRM:
172 if (sd->fd < 0)
173 return log_error_errno(SYNTHETIC_ERRNO(EBADF),
174 "Failed to re-activate DRM fd, as the fd was lost (maybe logind restart went wrong?)");
175
176 /* Device is kept open. Simply call drmSetMaster() and hope there is no-one else. In case it fails, we
177 * keep the device paused. Maybe at some point we have a drmStealMaster(). */
178 r = sd_drmsetmaster(sd->fd);
179 if (r < 0)
180 return r;
181 break;
182
183 case DEVICE_TYPE_EVDEV:
184 /* Evdev devices are revoked while inactive. Reopen it and we are fine. */
185 r = session_device_open(sd, true);
186 if (r < 0)
187 return r;
188
189 /* For evdev devices, the file descriptor might be left uninitialized. This might happen while resuming
190 * into a session and logind has been restarted right before. */
191 close_and_replace(sd->fd, r);
192 break;
193
194 case DEVICE_TYPE_UNKNOWN:
195 default:
196 /* fallback for devices without synchronizations */
197 break;
198 }
199
200 sd->active = true;
201 return 0;
202 }
203
204 static void session_device_stop(SessionDevice *sd) {
205 assert(sd);
206
207 if (!sd->active)
208 return;
209
210 switch (sd->type) {
211
212 case DEVICE_TYPE_DRM:
213 if (sd->fd < 0) {
214 log_error("Failed to de-activate DRM fd, as the fd was lost (maybe logind restart went wrong?)");
215 return;
216 }
217
218 /* On DRM devices we simply drop DRM-Master but keep it open.
219 * This allows the user to keep resources allocated. The
220 * CAP_SYS_ADMIN restriction to DRM-Master prevents users from
221 * circumventing this. */
222 sd_drmdropmaster(sd->fd);
223 break;
224
225 case DEVICE_TYPE_EVDEV:
226 /* Revoke access on evdev file-descriptors during deactivation.
227 * This will basically prevent any operations on the fd and
228 * cannot be undone. Good side is: it needs no CAP_SYS_ADMIN
229 * protection this way. */
230 sd_eviocrevoke(sd->fd);
231 break;
232
233 case DEVICE_TYPE_UNKNOWN:
234 default:
235 /* fallback for devices without synchronization */
236 break;
237 }
238
239 sd->active = false;
240 }
241
242 static DeviceType detect_device_type(sd_device *dev) {
243 const char *sysname;
244
245 if (sd_device_get_sysname(dev, &sysname) < 0)
246 return DEVICE_TYPE_UNKNOWN;
247
248 if (device_in_subsystem(dev, "drm")) {
249 if (startswith(sysname, "card"))
250 return DEVICE_TYPE_DRM;
251
252 } else if (device_in_subsystem(dev, "input")) {
253 if (startswith(sysname, "event"))
254 return DEVICE_TYPE_EVDEV;
255 }
256
257 return DEVICE_TYPE_UNKNOWN;
258 }
259
260 static int session_device_verify(SessionDevice *sd) {
261 _cleanup_(sd_device_unrefp) sd_device *p = NULL;
262 const char *sp, *node;
263 sd_device *dev;
264 int r;
265
266 r = sd_device_new_from_devnum(&p, 'c', sd->dev);
267 if (r < 0)
268 return r;
269
270 dev = p;
271
272 if (sd_device_get_syspath(dev, &sp) < 0 ||
273 sd_device_get_devname(dev, &node) < 0)
274 return -EINVAL;
275
276 /* detect device type so we can find the correct sysfs parent */
277 sd->type = detect_device_type(dev);
278
279 /* Prevent opening unsupported devices. Especially devices of
280 * subsystem "input" must be opened via the evdev node as
281 * we require EVIOCREVOKE. */
282 switch (sd->type) {
283 case DEVICE_TYPE_EVDEV:
284 /* for evdev devices we need the parent node as device */
285 if (sd_device_get_parent_with_subsystem_devtype(p, "input", NULL, &dev) < 0)
286 return -ENODEV;
287 if (sd_device_get_syspath(dev, &sp) < 0)
288 return -ENODEV;
289 break;
290
291 case DEVICE_TYPE_DRM:
292 break;
293
294 case DEVICE_TYPE_UNKNOWN:
295 default:
296 return -ENODEV;
297 }
298
299 /* search for an existing seat device and return it if available */
300 sd->device = hashmap_get(sd->session->manager->devices, sp);
301 if (!sd->device) {
302 /* The caller might have gotten the udev event before we were
303 * able to process it. Hence, fake the "add" event and let the
304 * logind-manager handle the new device. */
305 r = manager_process_seat_device(sd->session->manager, dev);
306 if (r < 0)
307 return r;
308
309 /* if it's still not available, then the device is invalid */
310 sd->device = hashmap_get(sd->session->manager->devices, sp);
311 if (!sd->device)
312 return -ENODEV;
313 }
314
315 if (sd->device->seat != sd->session->seat)
316 return -EPERM;
317
318 sd->node = strdup(node);
319 if (!sd->node)
320 return -ENOMEM;
321
322 return 0;
323 }
324
325 int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out) {
326 SessionDevice *sd;
327 int r;
328
329 assert(s);
330 assert(out);
331
332 if (!s->seat)
333 return -EPERM;
334
335 sd = new0(SessionDevice, 1);
336 if (!sd)
337 return -ENOMEM;
338
339 sd->session = s;
340 sd->dev = dev;
341 sd->fd = -EBADF;
342 sd->type = DEVICE_TYPE_UNKNOWN;
343
344 r = session_device_verify(sd);
345 if (r < 0)
346 goto error;
347
348 r = hashmap_put(s->devices, &sd->dev, sd);
349 if (r < 0)
350 goto error;
351
352 if (open_device) {
353 /* Open the device for the first time. We need a valid fd to pass back
354 * to the caller. If the session is not active, this _might_ immediately
355 * revoke access and thus invalidate the fd. But this is still needed
356 * to pass a valid fd back. */
357 sd->active = session_is_active(s);
358 r = session_device_open(sd, sd->active);
359 if (r < 0) {
360 /* EINVAL _may_ mean a master is active; retry inactive */
361 if (sd->active && r == -EINVAL) {
362 sd->active = false;
363 r = session_device_open(sd, false);
364 }
365 if (r < 0)
366 goto error;
367 }
368 sd->fd = r;
369 }
370
371 LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
372
373 *out = sd;
374 return 0;
375
376 error:
377 hashmap_remove(s->devices, &sd->dev);
378 free(sd->node);
379 free(sd);
380 return r;
381 }
382
383 SessionDevice *session_device_free(SessionDevice *sd) {
384 if (!sd)
385 return NULL;
386
387 /* Make sure to remove the pushed fd. */
388 if (sd->pushed_fd)
389 (void) notify_remove_fd_warnf("session-%s-device-%u-%u", sd->session->id, major(sd->dev), minor(sd->dev));
390
391 session_device_stop(sd);
392 session_device_notify(sd, SESSION_DEVICE_RELEASE);
393 safe_close(sd->fd);
394
395 LIST_REMOVE(sd_by_device, sd->device->session_devices, sd);
396
397 hashmap_remove(sd->session->devices, &sd->dev);
398
399 free(sd->node);
400
401 return mfree(sd);
402 }
403
404 void session_device_complete_pause(SessionDevice *sd) {
405 SessionDevice *iter;
406
407 if (!sd->active)
408 return;
409
410 session_device_stop(sd);
411
412 /* if not all devices are paused, wait for further completion events */
413 HASHMAP_FOREACH(iter, sd->session->devices)
414 if (iter->active)
415 return;
416
417 /* complete any pending session switch */
418 seat_complete_switch(sd->session->seat);
419 }
420
421 void session_device_resume_all(Session *s) {
422 SessionDevice *sd;
423
424 assert(s);
425
426 HASHMAP_FOREACH(sd, s->devices) {
427 if (sd->active)
428 continue;
429
430 if (session_device_start(sd) < 0)
431 continue;
432 if (session_device_save(sd) < 0)
433 continue;
434
435 session_device_notify(sd, SESSION_DEVICE_RESUME);
436 }
437 }
438
439 void session_device_pause_all(Session *s) {
440 SessionDevice *sd;
441
442 assert(s);
443
444 HASHMAP_FOREACH(sd, s->devices) {
445 if (!sd->active)
446 continue;
447
448 session_device_stop(sd);
449 session_device_notify(sd, SESSION_DEVICE_PAUSE);
450 }
451 }
452
453 unsigned session_device_try_pause_all(Session *s) {
454 unsigned num_pending = 0;
455 SessionDevice *sd;
456
457 assert(s);
458
459 HASHMAP_FOREACH(sd, s->devices) {
460 if (!sd->active)
461 continue;
462
463 session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE);
464 num_pending++;
465 }
466
467 return num_pending;
468 }
469
470 int session_device_save(SessionDevice *sd) {
471 const char *id;
472 int r;
473
474 assert(sd);
475
476 /* Store device fd in PID1. It will send it back to us on restart so revocation will continue to work. To make
477 * things simple, send fds for all type of devices even if they don't support the revocation mechanism so we
478 * don't have to handle them differently later.
479 *
480 * Note: for device supporting revocation, PID1 will drop a stored fd automatically if the corresponding device
481 * is revoked. */
482
483 if (sd->pushed_fd)
484 return 0;
485
486 /* Session ID does not contain separators. */
487 id = sd->session->id;
488 assert(*(id + strcspn(id, "-\n")) == '\0');
489
490 r = notify_push_fdf(sd->fd, "session-%s-device-%u-%u", id, major(sd->dev), minor(sd->dev));
491 if (r < 0)
492 return r;
493
494 sd->pushed_fd = true;
495 return 1;
496 }
497
498 void session_device_attach_fd(SessionDevice *sd, int fd, bool active) {
499 assert(fd >= 0);
500 assert(sd);
501 assert(sd->fd < 0);
502 assert(!sd->active);
503
504 sd->fd = fd;
505 sd->pushed_fd = true;
506 sd->active = active;
507 }