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