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