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