]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-session-device.c
logind: fix typo in comment
[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
197 if (sd->fd < 0) {
198 /* Open device if it isn't open yet */
199 sd->fd = session_device_open(sd, true);
200 if (sd->fd < 0)
201 return sd->fd;
202 } else {
203 /* Device is kept open. Simply call drmSetMaster() and hope there is no-one else. In case it fails, we
204 * keep the device paused. Maybe at some point we have a drmStealMaster(). */
205 r = sd_drmsetmaster(sd->fd);
206 if (r < 0)
207 return r;
208 }
209 break;
210
211 case DEVICE_TYPE_EVDEV:
212 /* Evdev devices are revoked while inactive. Reopen it and we are fine. */
213 r = session_device_open(sd, true);
214 if (r < 0)
215 return r;
216
217 /* For evdev devices, the file descriptor might be left uninitialized. This might happen while resuming
218 * into a session and logind has been restarted right before. */
219 safe_close(sd->fd);
220 sd->fd = r;
221 break;
222
223 case DEVICE_TYPE_UNKNOWN:
224 default:
225 /* fallback for devices without synchronizations */
226 break;
227 }
228
229 sd->active = true;
230 return 0;
231 }
232
233 static void session_device_stop(SessionDevice *sd) {
234 assert(sd);
235
236 if (!sd->active)
237 return;
238
239 switch (sd->type) {
240
241 case DEVICE_TYPE_DRM:
242 /* On DRM devices we simply drop DRM-Master but keep it open.
243 * This allows the user to keep resources allocated. The
244 * CAP_SYS_ADMIN restriction to DRM-Master prevents users from
245 * circumventing this. */
246 sd_drmdropmaster(sd->fd);
247 break;
248
249 case DEVICE_TYPE_EVDEV:
250 /* Revoke access on evdev file-descriptors during deactivation.
251 * This will basically prevent any operations on the fd and
252 * cannot be undone. Good side is: it needs no CAP_SYS_ADMIN
253 * protection this way. */
254 sd_eviocrevoke(sd->fd);
255 break;
256
257 case DEVICE_TYPE_UNKNOWN:
258 default:
259 /* fallback for devices without synchronization */
260 break;
261 }
262
263 sd->active = false;
264 }
265
266 static DeviceType detect_device_type(struct udev_device *dev) {
267 const char *sysname, *subsystem;
268 DeviceType type;
269
270 sysname = udev_device_get_sysname(dev);
271 subsystem = udev_device_get_subsystem(dev);
272 type = DEVICE_TYPE_UNKNOWN;
273
274 if (streq_ptr(subsystem, "drm")) {
275 if (startswith(sysname, "card"))
276 type = DEVICE_TYPE_DRM;
277 } else if (streq_ptr(subsystem, "input")) {
278 if (startswith(sysname, "event"))
279 type = DEVICE_TYPE_EVDEV;
280 }
281
282 return type;
283 }
284
285 static int session_device_verify(SessionDevice *sd) {
286 struct udev_device *dev, *p = NULL;
287 const char *sp, *node;
288 int r;
289
290 dev = udev_device_new_from_devnum(sd->session->manager->udev, 'c', sd->dev);
291 if (!dev)
292 return -ENODEV;
293
294 sp = udev_device_get_syspath(dev);
295 node = udev_device_get_devnode(dev);
296 if (!node) {
297 r = -EINVAL;
298 goto err_dev;
299 }
300
301 /* detect device type so we can find the correct sysfs parent */
302 sd->type = detect_device_type(dev);
303 if (sd->type == DEVICE_TYPE_UNKNOWN) {
304 r = -ENODEV;
305 goto err_dev;
306 } else if (sd->type == DEVICE_TYPE_EVDEV) {
307 /* for evdev devices we need the parent node as device */
308 p = dev;
309 dev = udev_device_get_parent_with_subsystem_devtype(p, "input", NULL);
310 if (!dev) {
311 r = -ENODEV;
312 goto err_dev;
313 }
314 sp = udev_device_get_syspath(dev);
315 } else if (sd->type != DEVICE_TYPE_DRM) {
316 /* Prevent opening unsupported devices. Especially devices of
317 * subsystem "input" must be opened via the evdev node as
318 * we require EVIOCREVOKE. */
319 r = -ENODEV;
320 goto err_dev;
321 }
322
323 /* search for an existing seat device and return it if available */
324 sd->device = hashmap_get(sd->session->manager->devices, sp);
325 if (!sd->device) {
326 /* The caller might have gotten the udev event before we were
327 * able to process it. Hence, fake the "add" event and let the
328 * logind-manager handle the new device. */
329 r = manager_process_seat_device(sd->session->manager, dev);
330 if (r < 0)
331 goto err_dev;
332
333 /* if it's still not available, then the device is invalid */
334 sd->device = hashmap_get(sd->session->manager->devices, sp);
335 if (!sd->device) {
336 r = -ENODEV;
337 goto err_dev;
338 }
339 }
340
341 if (sd->device->seat != sd->session->seat) {
342 r = -EPERM;
343 goto err_dev;
344 }
345
346 sd->node = strdup(node);
347 if (!sd->node) {
348 r = -ENOMEM;
349 goto err_dev;
350 }
351
352 r = 0;
353 err_dev:
354 udev_device_unref(p ? : dev);
355 return r;
356 }
357
358 int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out) {
359 SessionDevice *sd;
360 int r;
361
362 assert(s);
363 assert(out);
364
365 if (!s->seat)
366 return -EPERM;
367
368 sd = new0(SessionDevice, 1);
369 if (!sd)
370 return -ENOMEM;
371
372 sd->session = s;
373 sd->dev = dev;
374 sd->fd = -1;
375 sd->type = DEVICE_TYPE_UNKNOWN;
376
377 r = session_device_verify(sd);
378 if (r < 0)
379 goto error;
380
381 r = hashmap_put(s->devices, &sd->dev, sd);
382 if (r < 0)
383 goto error;
384
385 if (open_device) {
386 /* Open the device for the first time. We need a valid fd to pass back
387 * to the caller. If the session is not active, this _might_ immediately
388 * revoke access and thus invalidate the fd. But this is still needed
389 * to pass a valid fd back. */
390 sd->active = session_is_active(s);
391 r = session_device_open(sd, sd->active);
392 if (r < 0) {
393 /* EINVAL _may_ mean a master is active; retry inactive */
394 if (sd->active && r == -EINVAL) {
395 sd->active = false;
396 r = session_device_open(sd, false);
397 }
398 if (r < 0)
399 goto error;
400 }
401 sd->fd = r;
402 }
403
404 LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
405
406 *out = sd;
407 return 0;
408
409 error:
410 hashmap_remove(s->devices, &sd->dev);
411 free(sd->node);
412 free(sd);
413 return r;
414 }
415
416 void session_device_free(SessionDevice *sd) {
417 assert(sd);
418
419 if (sd->pushed_fd) {
420 const char *m;
421
422 /* Remove the pushed fd again, just in case. */
423
424 m = strjoina("FDSTOREREMOVE=1\n"
425 "FDNAME=session-", sd->session->id);
426
427 (void) sd_notify(false, m);
428 }
429
430 session_device_stop(sd);
431 session_device_notify(sd, SESSION_DEVICE_RELEASE);
432 safe_close(sd->fd);
433
434 LIST_REMOVE(sd_by_device, sd->device->session_devices, sd);
435
436 hashmap_remove(sd->session->devices, &sd->dev);
437
438 free(sd->node);
439 free(sd);
440 }
441
442 void session_device_complete_pause(SessionDevice *sd) {
443 SessionDevice *iter;
444 Iterator i;
445
446 if (!sd->active)
447 return;
448
449 session_device_stop(sd);
450
451 /* if not all devices are paused, wait for further completion events */
452 HASHMAP_FOREACH(iter, sd->session->devices, i)
453 if (iter->active)
454 return;
455
456 /* complete any pending session switch */
457 seat_complete_switch(sd->session->seat);
458 }
459
460 void session_device_resume_all(Session *s) {
461 SessionDevice *sd;
462 Iterator i;
463
464 assert(s);
465
466 HASHMAP_FOREACH(sd, s->devices, i) {
467 if (sd->active)
468 continue;
469
470 if (session_device_start(sd) < 0)
471 continue;
472 if (session_device_save(sd) < 0)
473 continue;
474
475 session_device_notify(sd, SESSION_DEVICE_RESUME);
476 }
477 }
478
479 void session_device_pause_all(Session *s) {
480 SessionDevice *sd;
481 Iterator i;
482
483 assert(s);
484
485 HASHMAP_FOREACH(sd, s->devices, i) {
486 if (!sd->active)
487 continue;
488
489 session_device_stop(sd);
490 session_device_notify(sd, SESSION_DEVICE_PAUSE);
491 }
492 }
493
494 unsigned int session_device_try_pause_all(Session *s) {
495 unsigned num_pending = 0;
496 SessionDevice *sd;
497 Iterator i;
498
499 assert(s);
500
501 HASHMAP_FOREACH(sd, s->devices, i) {
502 if (!sd->active)
503 continue;
504
505 session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE);
506 num_pending++;
507 }
508
509 return num_pending;
510 }
511
512 int session_device_save(SessionDevice *sd) {
513 const char *m;
514 int r;
515
516 assert(sd);
517
518 /* Store device fd in PID1. It will send it back to us on restart so revocation will continue to work. To make
519 * things simple, send fds for all type of devices even if they don't support the revocation mechanism so we
520 * don't have to handle them differently later.
521 *
522 * Note: for device supporting revocation, PID1 will drop a stored fd automatically if the corresponding device
523 * is revoked. */
524
525 if (sd->pushed_fd)
526 return 0;
527
528 m = strjoina("FDSTORE=1\n"
529 "FDNAME=session", sd->session->id);
530
531 r = sd_pid_notify_with_fds(0, false, m, &sd->fd, 1);
532 if (r < 0)
533 return r;
534
535 sd->pushed_fd = true;
536 return 1;
537 }
538
539 void session_device_attach_fd(SessionDevice *sd, int fd, bool active) {
540 assert(fd >= 0);
541 assert(sd);
542 assert(sd->fd < 0);
543 assert(!sd->active);
544
545 sd->fd = fd;
546 sd->active = active;
547 }