]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/logind-session-device.c
Merge pull request #12753 from jrouleau/fix/hibernate-resume-timeout
[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"
6ecda0fb 10#include "sd-daemon.h"
b4bbcaa9 11
b5efdb8a 12#include "alloc-util.h"
cc377381 13#include "bus-util.h"
3ffd4af2 14#include "fd-util.h"
6ecda0fb 15#include "logind-session-dbus.h"
cc377381 16#include "logind-session-device.h"
3ffd4af2 17#include "missing.h"
aed24c4c 18#include "parse-util.h"
3ffd4af2 19#include "util.h"
118ecf32
DH
20
21enum SessionDeviceNotifications {
22 SESSION_DEVICE_RESUME,
23 SESSION_DEVICE_TRY_PAUSE,
24 SESSION_DEVICE_PAUSE,
25 SESSION_DEVICE_RELEASE,
26};
27
cc377381 28static int session_device_notify(SessionDevice *sd, enum SessionDeviceNotifications type) {
4afd3348 29 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
118ecf32
DH
30 _cleanup_free_ char *path = NULL;
31 const char *t = NULL;
081dfa85 32 uint32_t major, minor;
cc377381 33 int r;
118ecf32
DH
34
35 assert(sd);
36
081dfa85
DH
37 major = major(sd->dev);
38 minor = minor(sd->dev);
39
118ecf32 40 if (!sd->session->controller)
cc377381 41 return 0;
118ecf32
DH
42
43 path = session_bus_path(sd->session);
44 if (!path)
cc377381 45 return -ENOMEM;
118ecf32 46
cc377381 47 r = sd_bus_message_new_signal(
151b9b96
LP
48 sd->session->manager->bus,
49 &m, path,
cc377381 50 "org.freedesktop.login1.Session",
151b9b96 51 (type == SESSION_DEVICE_RESUME) ? "ResumeDevice" : "PauseDevice");
118ecf32 52 if (!m)
cc377381 53 return r;
118ecf32 54
cc377381
LP
55 r = sd_bus_message_set_destination(m, sd->session->controller);
56 if (r < 0)
57 return r;
118ecf32
DH
58
59 switch (type) {
864fe630 60
118ecf32 61 case SESSION_DEVICE_RESUME:
cc377381
LP
62 r = sd_bus_message_append(m, "uuh", major, minor, sd->fd);
63 if (r < 0)
64 return r;
118ecf32 65 break;
864fe630 66
118ecf32
DH
67 case SESSION_DEVICE_TRY_PAUSE:
68 t = "pause";
69 break;
864fe630 70
118ecf32
DH
71 case SESSION_DEVICE_PAUSE:
72 t = "force";
73 break;
864fe630 74
118ecf32
DH
75 case SESSION_DEVICE_RELEASE:
76 t = "gone";
77 break;
864fe630 78
118ecf32 79 default:
cc377381 80 return -EINVAL;
118ecf32
DH
81 }
82
cc377381
LP
83 if (t) {
84 r = sd_bus_message_append(m, "uus", major, minor, t);
85 if (r < 0)
86 return r;
87 }
118ecf32 88
cc377381 89 return sd_bus_send(sd->session->manager->bus, m, NULL);
118ecf32
DH
90}
91
a3ddf73c 92static void sd_eviocrevoke(int fd) {
5d5330a8 93 static bool warned = false;
118ecf32
DH
94
95 assert(fd >= 0);
96
5d5330a8
LP
97 if (ioctl(fd, EVIOCREVOKE, NULL) < 0) {
98
99 if (errno == EINVAL && !warned) {
100 log_warning_errno(errno, "Kernel does not support evdev-revocation: %m");
118ecf32 101 warned = true;
118ecf32
DH
102 }
103 }
118ecf32
DH
104}
105
106static int sd_drmsetmaster(int fd) {
118ecf32
DH
107 assert(fd >= 0);
108
864fe630 109 if (ioctl(fd, DRM_IOCTL_SET_MASTER, 0) < 0)
118ecf32
DH
110 return -errno;
111
112 return 0;
113}
114
115static int sd_drmdropmaster(int fd) {
118ecf32
DH
116 assert(fd >= 0);
117
864fe630 118 if (ioctl(fd, DRM_IOCTL_DROP_MASTER, 0) < 0)
118ecf32
DH
119 return -errno;
120
121 return 0;
122}
123
124static int session_device_open(SessionDevice *sd, bool active) {
c2e5d024 125 int fd, r;
118ecf32 126
864fe630 127 assert(sd);
118ecf32 128 assert(sd->type != DEVICE_TYPE_UNKNOWN);
864fe630 129 assert(sd->node);
118ecf32
DH
130
131 /* open device and try to get an udev_device from it */
132 fd = open(sd->node, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
133 if (fd < 0)
134 return -errno;
135
136 switch (sd->type) {
864fe630 137
118ecf32 138 case DEVICE_TYPE_DRM:
c2e5d024 139 if (active) {
864fe630
LP
140 /* Weird legacy DRM semantics might return an error even though we're master. No way to detect
141 * that so fail at all times and let caller retry in inactive state. */
c2e5d024
DH
142 r = sd_drmsetmaster(fd);
143 if (r < 0) {
d1107170 144 close_nointr(fd);
c2e5d024
DH
145 return r;
146 }
864fe630
LP
147 } else
148 /* DRM-Master is granted to the first user who opens a device automatically (ughh,
149 * racy!). Hence, we just drop DRM-Master in case we were the first. */
4804600b 150 (void) sd_drmdropmaster(fd);
118ecf32 151 break;
864fe630 152
118ecf32
DH
153 case DEVICE_TYPE_EVDEV:
154 if (!active)
155 sd_eviocrevoke(fd);
156 break;
864fe630 157
118ecf32
DH
158 case DEVICE_TYPE_UNKNOWN:
159 default:
5238e957 160 /* fallback for devices without synchronizations */
118ecf32
DH
161 break;
162 }
163
164 return fd;
165}
166
167static int session_device_start(SessionDevice *sd) {
168 int r;
169
170 assert(sd);
171 assert(session_is_active(sd->session));
172
173 if (sd->active)
174 return 0;
175
176 switch (sd->type) {
864fe630 177
118ecf32 178 case DEVICE_TYPE_DRM:
baaa35ad
ZJS
179 if (sd->fd < 0)
180 return log_error_errno(SYNTHETIC_ERRNO(EBADF),
181 "Failed to re-activate DRM fd, as the fd was lost (maybe logind restart went wrong?)");
f2705337
AJ
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;
118ecf32 188 break;
864fe630 189
118ecf32 190 case DEVICE_TYPE_EVDEV:
864fe630 191 /* Evdev devices are revoked while inactive. Reopen it and we are fine. */
118ecf32
DH
192 r = session_device_open(sd, true);
193 if (r < 0)
194 return r;
864fe630
LP
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. */
aed24c4c 198 safe_close(sd->fd);
118ecf32
DH
199 sd->fd = r;
200 break;
864fe630 201
118ecf32
DH
202 case DEVICE_TYPE_UNKNOWN:
203 default:
340aff15 204 /* fallback for devices without synchronizations */
118ecf32
DH
205 break;
206 }
207
208 sd->active = true;
209 return 0;
210}
211
212static void session_device_stop(SessionDevice *sd) {
213 assert(sd);
214
215 if (!sd->active)
216 return;
217
218 switch (sd->type) {
340aff15 219
118ecf32 220 case DEVICE_TYPE_DRM:
f2705337
AJ
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
118ecf32
DH
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;
340aff15 232
118ecf32
DH
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;
340aff15 240
118ecf32
DH
241 case DEVICE_TYPE_UNKNOWN:
242 default:
243 /* fallback for devices without synchronization */
244 break;
245 }
246
247 sd->active = false;
248}
249
4f209af7 250static DeviceType detect_device_type(sd_device *dev) {
118ecf32 251 const char *sysname, *subsystem;
4f209af7 252 DeviceType type = DEVICE_TYPE_UNKNOWN;
118ecf32 253
4f209af7
YW
254 if (sd_device_get_sysname(dev, &sysname) < 0 ||
255 sd_device_get_subsystem(dev, &subsystem) < 0)
256 return type;
118ecf32 257
403660c5 258 if (streq(subsystem, "drm")) {
118ecf32
DH
259 if (startswith(sysname, "card"))
260 type = DEVICE_TYPE_DRM;
403660c5 261 } else if (streq(subsystem, "input")) {
118ecf32
DH
262 if (startswith(sysname, "event"))
263 type = DEVICE_TYPE_EVDEV;
264 }
265
266 return type;
267}
268
269static int session_device_verify(SessionDevice *sd) {
4f209af7 270 _cleanup_(sd_device_unrefp) sd_device *p = NULL;
c679e12a 271 const char *sp, *node;
4f209af7 272 sd_device *dev;
118ecf32
DH
273 int r;
274
c679e12a
YW
275 r = sd_device_new_from_devnum(&p, 'c', sd->dev);
276 if (r < 0)
277 return r;
118ecf32 278
4f209af7
YW
279 dev = p;
280
c679e12a
YW
281 if (sd_device_get_syspath(dev, &sp) < 0 ||
282 sd_device_get_devname(dev, &node) < 0)
4f209af7 283 return -EINVAL;
118ecf32
DH
284
285 /* detect device type so we can find the correct sysfs parent */
286 sd->type = detect_device_type(dev);
4f209af7
YW
287 if (sd->type == DEVICE_TYPE_UNKNOWN)
288 return -ENODEV;
289
290 else if (sd->type == DEVICE_TYPE_EVDEV) {
118ecf32 291 /* for evdev devices we need the parent node as device */
4f209af7
YW
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)
118ecf32
DH
298 /* Prevent opening unsupported devices. Especially devices of
299 * subsystem "input" must be opened via the evdev node as
300 * we require EVIOCREVOKE. */
4f209af7 301 return -ENODEV;
118ecf32
DH
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)
4f209af7 311 return r;
118ecf32
DH
312
313 /* if it's still not available, then the device is invalid */
314 sd->device = hashmap_get(sd->session->manager->devices, sp);
4f209af7
YW
315 if (!sd->device)
316 return -ENODEV;
118ecf32
DH
317 }
318
4f209af7
YW
319 if (sd->device->seat != sd->session->seat)
320 return -EPERM;
118ecf32
DH
321
322 sd->node = strdup(node);
4f209af7
YW
323 if (!sd->node)
324 return -ENOMEM;
118ecf32 325
4f209af7 326 return 0;
118ecf32
DH
327}
328
aed24c4c 329int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out) {
118ecf32
DH
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
831dedef 352 r = hashmap_put(s->devices, &sd->dev, sd);
e38aa664 353 if (r < 0)
118ecf32 354 goto error;
118ecf32 355
aed24c4c
FB
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;
c2e5d024 371 }
aed24c4c 372 sd->fd = r;
c2e5d024 373 }
118ecf32 374
71fda00f 375 LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
118ecf32
DH
376
377 *out = sd;
378 return 0;
379
380error:
831dedef 381 hashmap_remove(s->devices, &sd->dev);
118ecf32
DH
382 free(sd->node);
383 free(sd);
384 return r;
385}
386
387void session_device_free(SessionDevice *sd) {
388 assert(sd);
389
1bef256c 390 /* Make sure to remove the pushed fd. */
883d1b01
LP
391 if (sd->pushed_fd)
392 (void) sd_notifyf(false,
393 "FDSTOREREMOVE=1\n"
394 "FDNAME=session-%s-device-%u-%u",
395 sd->session->id, major(sd->dev), minor(sd->dev));
4050e479 396
118ecf32
DH
397 session_device_stop(sd);
398 session_device_notify(sd, SESSION_DEVICE_RELEASE);
4d219f53 399 safe_close(sd->fd);
118ecf32 400
71fda00f 401 LIST_REMOVE(sd_by_device, sd->device->session_devices, sd);
118ecf32 402
831dedef 403 hashmap_remove(sd->session->devices, &sd->dev);
118ecf32
DH
404
405 free(sd->node);
406 free(sd);
407}
408
409void session_device_complete_pause(SessionDevice *sd) {
d7bd01b5
DH
410 SessionDevice *iter;
411 Iterator i;
412
118ecf32
DH
413 if (!sd->active)
414 return;
415
416 session_device_stop(sd);
d7bd01b5
DH
417
418 /* if not all devices are paused, wait for further completion events */
419 HASHMAP_FOREACH(iter, sd->session->devices, i)
420 if (iter->active)
421 return;
422
423 /* complete any pending session switch */
424 seat_complete_switch(sd->session->seat);
118ecf32
DH
425}
426
427void session_device_resume_all(Session *s) {
428 SessionDevice *sd;
429 Iterator i;
118ecf32
DH
430
431 assert(s);
432
433 HASHMAP_FOREACH(sd, s->devices, i) {
d7ba71f4
LP
434 if (sd->active)
435 continue;
436
437 if (session_device_start(sd) < 0)
438 continue;
439 if (session_device_save(sd) < 0)
440 continue;
340aff15 441
d7ba71f4 442 session_device_notify(sd, SESSION_DEVICE_RESUME);
118ecf32
DH
443 }
444}
445
446void session_device_pause_all(Session *s) {
447 SessionDevice *sd;
448 Iterator i;
449
450 assert(s);
451
452 HASHMAP_FOREACH(sd, s->devices, i) {
d7ba71f4
LP
453 if (!sd->active)
454 continue;
455
456 session_device_stop(sd);
457 session_device_notify(sd, SESSION_DEVICE_PAUSE);
118ecf32
DH
458 }
459}
d7bd01b5 460
14cb109d 461unsigned session_device_try_pause_all(Session *s) {
d7ba71f4 462 unsigned num_pending = 0;
d7bd01b5
DH
463 SessionDevice *sd;
464 Iterator i;
d7bd01b5
DH
465
466 assert(s);
467
468 HASHMAP_FOREACH(sd, s->devices, i) {
d7ba71f4
LP
469 if (!sd->active)
470 continue;
471
472 session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE);
473 num_pending++;
d7bd01b5
DH
474 }
475
476 return num_pending;
477}
aed24c4c
FB
478
479int session_device_save(SessionDevice *sd) {
1bef256c
AJ
480 _cleanup_free_ char *m = NULL;
481 const char *id;
aed24c4c
FB
482 int r;
483
484 assert(sd);
485
4050e479
LP
486 /* Store device fd in PID1. It will send it back to us on restart so revocation will continue to work. To make
487 * things simple, send fds for all type of devices even if they don't support the revocation mechanism so we
488 * don't have to handle them differently later.
aed24c4c 489 *
4050e479
LP
490 * Note: for device supporting revocation, PID1 will drop a stored fd automatically if the corresponding device
491 * is revoked. */
492
493 if (sd->pushed_fd)
494 return 0;
f86fae61 495
1bef256c
AJ
496 /* Session ID does not contain separators. */
497 id = sd->session->id;
498 assert(*(id + strcspn(id, "-\n")) == '\0');
499
500 r = asprintf(&m, "FDSTORE=1\n"
501 "FDNAME=session-%s-device-%u-%u\n",
502 id, major(sd->dev), minor(sd->dev));
503 if (r < 0)
504 return r;
4050e479
LP
505
506 r = sd_pid_notify_with_fds(0, false, m, &sd->fd, 1);
aed24c4c 507 if (r < 0)
4050e479 508 return r;
aed24c4c 509
4050e479
LP
510 sd->pushed_fd = true;
511 return 1;
aed24c4c
FB
512}
513
514void session_device_attach_fd(SessionDevice *sd, int fd, bool active) {
4c9cb12c 515 assert(fd >= 0);
aed24c4c
FB
516 assert(sd);
517 assert(sd->fd < 0);
518 assert(!sd->active);
519
520 sd->fd = fd;
f8f9419e 521 sd->pushed_fd = true;
aed24c4c
FB
522 sd->active = active;
523}