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