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