]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-session-device.c
Merge pull request #8025 from sourcejedi/pid1_journal_or2
[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 case SESSION_DEVICE_RESUME:
78 r = sd_bus_message_append(m, "uuh", major, minor, sd->fd);
79 if (r < 0)
80 return r;
81 break;
82 case SESSION_DEVICE_TRY_PAUSE:
83 t = "pause";
84 break;
85 case SESSION_DEVICE_PAUSE:
86 t = "force";
87 break;
88 case SESSION_DEVICE_RELEASE:
89 t = "gone";
90 break;
91 default:
92 return -EINVAL;
93 }
94
95 if (t) {
96 r = sd_bus_message_append(m, "uus", major, minor, t);
97 if (r < 0)
98 return r;
99 }
100
101 return sd_bus_send(sd->session->manager->bus, m, NULL);
102 }
103
104 static int sd_eviocrevoke(int fd) {
105 static bool warned;
106 int r;
107
108 assert(fd >= 0);
109
110 r = ioctl(fd, EVIOCREVOKE, NULL);
111 if (r < 0) {
112 r = -errno;
113 if (r == -EINVAL && !warned) {
114 warned = true;
115 log_warning("kernel does not support evdev-revocation");
116 }
117 }
118
119 return 0;
120 }
121
122 static int sd_drmsetmaster(int fd) {
123 int r;
124
125 assert(fd >= 0);
126
127 r = ioctl(fd, DRM_IOCTL_SET_MASTER, 0);
128 if (r < 0)
129 return -errno;
130
131 return 0;
132 }
133
134 static int sd_drmdropmaster(int fd) {
135 int r;
136
137 assert(fd >= 0);
138
139 r = ioctl(fd, DRM_IOCTL_DROP_MASTER, 0);
140 if (r < 0)
141 return -errno;
142
143 return 0;
144 }
145
146 static int session_device_open(SessionDevice *sd, bool active) {
147 int fd, r;
148
149 assert(sd->type != DEVICE_TYPE_UNKNOWN);
150
151 /* open device and try to get an udev_device from it */
152 fd = open(sd->node, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
153 if (fd < 0)
154 return -errno;
155
156 switch (sd->type) {
157 case DEVICE_TYPE_DRM:
158 if (active) {
159 /* Weird legacy DRM semantics might return an error
160 * even though we're master. No way to detect that so
161 * fail at all times and let caller retry in inactive
162 * state. */
163 r = sd_drmsetmaster(fd);
164 if (r < 0) {
165 close_nointr(fd);
166 return r;
167 }
168 } else {
169 /* DRM-Master is granted to the first user who opens a
170 * device automatically (ughh, racy!). Hence, we just
171 * drop DRM-Master in case we were the first. */
172 sd_drmdropmaster(fd);
173 }
174 break;
175 case DEVICE_TYPE_EVDEV:
176 if (!active)
177 sd_eviocrevoke(fd);
178 break;
179 case DEVICE_TYPE_UNKNOWN:
180 default:
181 /* fallback for devices wihout synchronizations */
182 break;
183 }
184
185 return fd;
186 }
187
188 static int session_device_start(SessionDevice *sd) {
189 int r;
190
191 assert(sd);
192 assert(session_is_active(sd->session));
193
194 if (sd->active)
195 return 0;
196
197 switch (sd->type) {
198 case DEVICE_TYPE_DRM:
199 /* Device is kept open. Simply call drmSetMaster() and hope
200 * there is no-one else. In case it fails, we keep the device
201 * paused. Maybe at some point we have a drmStealMaster(). */
202 r = sd_drmsetmaster(sd->fd);
203 if (r < 0)
204 return r;
205 break;
206 case DEVICE_TYPE_EVDEV:
207 /* Evdev devices are revoked while inactive. Reopen it and we
208 * are fine. */
209 r = session_device_open(sd, true);
210 if (r < 0)
211 return r;
212 /* For evdev devices, the file descriptor might be left
213 * uninitialized. This might happen while resuming into a
214 * session and logind has been restarted right before. */
215 safe_close(sd->fd);
216 sd->fd = r;
217 break;
218 case DEVICE_TYPE_UNKNOWN:
219 default:
220 /* fallback for devices wihout synchronizations */
221 break;
222 }
223
224 sd->active = true;
225 return 0;
226 }
227
228 static void session_device_stop(SessionDevice *sd) {
229 assert(sd);
230
231 if (!sd->active)
232 return;
233
234 switch (sd->type) {
235 case DEVICE_TYPE_DRM:
236 /* On DRM devices we simply drop DRM-Master but keep it open.
237 * This allows the user to keep resources allocated. The
238 * CAP_SYS_ADMIN restriction to DRM-Master prevents users from
239 * circumventing this. */
240 sd_drmdropmaster(sd->fd);
241 break;
242 case DEVICE_TYPE_EVDEV:
243 /* Revoke access on evdev file-descriptors during deactivation.
244 * This will basically prevent any operations on the fd and
245 * cannot be undone. Good side is: it needs no CAP_SYS_ADMIN
246 * protection this way. */
247 sd_eviocrevoke(sd->fd);
248 break;
249 case DEVICE_TYPE_UNKNOWN:
250 default:
251 /* fallback for devices without synchronization */
252 break;
253 }
254
255 sd->active = false;
256 }
257
258 static DeviceType detect_device_type(struct udev_device *dev) {
259 const char *sysname, *subsystem;
260 DeviceType type;
261
262 sysname = udev_device_get_sysname(dev);
263 subsystem = udev_device_get_subsystem(dev);
264 type = DEVICE_TYPE_UNKNOWN;
265
266 if (streq_ptr(subsystem, "drm")) {
267 if (startswith(sysname, "card"))
268 type = DEVICE_TYPE_DRM;
269 } else if (streq_ptr(subsystem, "input")) {
270 if (startswith(sysname, "event"))
271 type = DEVICE_TYPE_EVDEV;
272 }
273
274 return type;
275 }
276
277 static int session_device_verify(SessionDevice *sd) {
278 struct udev_device *dev, *p = NULL;
279 const char *sp, *node;
280 int r;
281
282 dev = udev_device_new_from_devnum(sd->session->manager->udev, 'c', sd->dev);
283 if (!dev)
284 return -ENODEV;
285
286 sp = udev_device_get_syspath(dev);
287 node = udev_device_get_devnode(dev);
288 if (!node) {
289 r = -EINVAL;
290 goto err_dev;
291 }
292
293 /* detect device type so we can find the correct sysfs parent */
294 sd->type = detect_device_type(dev);
295 if (sd->type == DEVICE_TYPE_UNKNOWN) {
296 r = -ENODEV;
297 goto err_dev;
298 } else if (sd->type == DEVICE_TYPE_EVDEV) {
299 /* for evdev devices we need the parent node as device */
300 p = dev;
301 dev = udev_device_get_parent_with_subsystem_devtype(p, "input", NULL);
302 if (!dev) {
303 r = -ENODEV;
304 goto err_dev;
305 }
306 sp = udev_device_get_syspath(dev);
307 } else if (sd->type != DEVICE_TYPE_DRM) {
308 /* Prevent opening unsupported devices. Especially devices of
309 * subsystem "input" must be opened via the evdev node as
310 * we require EVIOCREVOKE. */
311 r = -ENODEV;
312 goto err_dev;
313 }
314
315 /* search for an existing seat device and return it if available */
316 sd->device = hashmap_get(sd->session->manager->devices, sp);
317 if (!sd->device) {
318 /* The caller might have gotten the udev event before we were
319 * able to process it. Hence, fake the "add" event and let the
320 * logind-manager handle the new device. */
321 r = manager_process_seat_device(sd->session->manager, dev);
322 if (r < 0)
323 goto err_dev;
324
325 /* if it's still not available, then the device is invalid */
326 sd->device = hashmap_get(sd->session->manager->devices, sp);
327 if (!sd->device) {
328 r = -ENODEV;
329 goto err_dev;
330 }
331 }
332
333 if (sd->device->seat != sd->session->seat) {
334 r = -EPERM;
335 goto err_dev;
336 }
337
338 sd->node = strdup(node);
339 if (!sd->node) {
340 r = -ENOMEM;
341 goto err_dev;
342 }
343
344 r = 0;
345 err_dev:
346 udev_device_unref(p ? : dev);
347 return r;
348 }
349
350 int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out) {
351 SessionDevice *sd;
352 int r;
353
354 assert(s);
355 assert(out);
356
357 if (!s->seat)
358 return -EPERM;
359
360 sd = new0(SessionDevice, 1);
361 if (!sd)
362 return -ENOMEM;
363
364 sd->session = s;
365 sd->dev = dev;
366 sd->fd = -1;
367 sd->type = DEVICE_TYPE_UNKNOWN;
368
369 r = session_device_verify(sd);
370 if (r < 0)
371 goto error;
372
373 r = hashmap_put(s->devices, &sd->dev, sd);
374 if (r < 0) {
375 r = -ENOMEM;
376 goto error;
377 }
378
379 if (open_device) {
380 /* Open the device for the first time. We need a valid fd to pass back
381 * to the caller. If the session is not active, this _might_ immediately
382 * revoke access and thus invalidate the fd. But this is still needed
383 * to pass a valid fd back. */
384 sd->active = session_is_active(s);
385 r = session_device_open(sd, sd->active);
386 if (r < 0) {
387 /* EINVAL _may_ mean a master is active; retry inactive */
388 if (sd->active && r == -EINVAL) {
389 sd->active = false;
390 r = session_device_open(sd, false);
391 }
392 if (r < 0)
393 goto error;
394 }
395 sd->fd = r;
396 }
397
398 LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
399
400 *out = sd;
401 return 0;
402
403 error:
404 hashmap_remove(s->devices, &sd->dev);
405 free(sd->node);
406 free(sd);
407 return r;
408 }
409
410 void session_device_free(SessionDevice *sd) {
411 assert(sd);
412
413 if (sd->pushed_fd) {
414 const char *m;
415
416 /* Remove the pushed fd again, just in case. */
417
418 m = strjoina("FDSTOREREMOVE=1\n"
419 "FDNAME=session-", sd->session->id);
420
421 (void) sd_notify(false, m);
422 }
423
424 session_device_stop(sd);
425 session_device_notify(sd, SESSION_DEVICE_RELEASE);
426 close_nointr(sd->fd);
427
428 LIST_REMOVE(sd_by_device, sd->device->session_devices, sd);
429
430 hashmap_remove(sd->session->devices, &sd->dev);
431
432 free(sd->node);
433 free(sd);
434 }
435
436 void session_device_complete_pause(SessionDevice *sd) {
437 SessionDevice *iter;
438 Iterator i;
439
440 if (!sd->active)
441 return;
442
443 session_device_stop(sd);
444
445 /* if not all devices are paused, wait for further completion events */
446 HASHMAP_FOREACH(iter, sd->session->devices, i)
447 if (iter->active)
448 return;
449
450 /* complete any pending session switch */
451 seat_complete_switch(sd->session->seat);
452 }
453
454 void session_device_resume_all(Session *s) {
455 SessionDevice *sd;
456 Iterator i;
457
458 assert(s);
459
460 HASHMAP_FOREACH(sd, s->devices, i) {
461 if (!sd->active) {
462 if (session_device_start(sd) < 0)
463 continue;
464 if (session_device_save(sd) < 0)
465 continue;
466 session_device_notify(sd, SESSION_DEVICE_RESUME);
467 }
468 }
469 }
470
471 void session_device_pause_all(Session *s) {
472 SessionDevice *sd;
473 Iterator i;
474
475 assert(s);
476
477 HASHMAP_FOREACH(sd, s->devices, i) {
478 if (sd->active) {
479 session_device_stop(sd);
480 session_device_notify(sd, SESSION_DEVICE_PAUSE);
481 }
482 }
483 }
484
485 unsigned int session_device_try_pause_all(Session *s) {
486 SessionDevice *sd;
487 Iterator i;
488 unsigned int num_pending = 0;
489
490 assert(s);
491
492 HASHMAP_FOREACH(sd, s->devices, i) {
493 if (sd->active) {
494 session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE);
495 ++num_pending;
496 }
497 }
498
499 return num_pending;
500 }
501
502 int session_device_save(SessionDevice *sd) {
503 const char *m;
504 int r;
505
506 assert(sd);
507
508 /* Store device fd in PID1. It will send it back to us on restart so revocation will continue to work. To make
509 * things simple, send fds for all type of devices even if they don't support the revocation mechanism so we
510 * don't have to handle them differently later.
511 *
512 * Note: for device supporting revocation, PID1 will drop a stored fd automatically if the corresponding device
513 * is revoked. */
514
515 if (sd->pushed_fd)
516 return 0;
517
518 m = strjoina("FDSTORE=1\n"
519 "FDNAME=session", sd->session->id);
520
521 r = sd_pid_notify_with_fds(0, false, m, &sd->fd, 1);
522 if (r < 0)
523 return r;
524
525 sd->pushed_fd = true;
526 return 1;
527 }
528
529 void session_device_attach_fd(SessionDevice *sd, int fd, bool active) {
530 assert(fd > 0);
531 assert(sd);
532 assert(sd->fd < 0);
533 assert(!sd->active);
534
535 sd->fd = fd;
536 sd->active = active;
537 }