]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/logind-session-device.c
login: don't remove all devices from PID1 when only one was removed
[thirdparty/systemd.git] / src / login / logind-session-device.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
118ecf32
DH
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
118ecf32 21#include <fcntl.h>
118ecf32 22#include <linux/input.h>
118ecf32
DH
23#include <string.h>
24#include <sys/ioctl.h>
118ecf32 25#include <sys/types.h>
118ecf32 26
b4bbcaa9
TA
27#include "libudev.h"
28
b5efdb8a 29#include "alloc-util.h"
cc377381 30#include "bus-util.h"
3ffd4af2 31#include "fd-util.h"
cc377381 32#include "logind-session-device.h"
3ffd4af2 33#include "missing.h"
aed24c4c
FB
34#include "parse-util.h"
35#include "sd-daemon.h"
3ffd4af2 36#include "util.h"
118ecf32
DH
37
38enum SessionDeviceNotifications {
39 SESSION_DEVICE_RESUME,
40 SESSION_DEVICE_TRY_PAUSE,
41 SESSION_DEVICE_PAUSE,
42 SESSION_DEVICE_RELEASE,
43};
44
cc377381 45static int session_device_notify(SessionDevice *sd, enum SessionDeviceNotifications type) {
4afd3348 46 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
118ecf32
DH
47 _cleanup_free_ char *path = NULL;
48 const char *t = NULL;
081dfa85 49 uint32_t major, minor;
cc377381 50 int r;
118ecf32
DH
51
52 assert(sd);
53
081dfa85
DH
54 major = major(sd->dev);
55 minor = minor(sd->dev);
56
118ecf32 57 if (!sd->session->controller)
cc377381 58 return 0;
118ecf32
DH
59
60 path = session_bus_path(sd->session);
61 if (!path)
cc377381 62 return -ENOMEM;
118ecf32 63
cc377381 64 r = sd_bus_message_new_signal(
151b9b96
LP
65 sd->session->manager->bus,
66 &m, path,
cc377381 67 "org.freedesktop.login1.Session",
151b9b96 68 (type == SESSION_DEVICE_RESUME) ? "ResumeDevice" : "PauseDevice");
118ecf32 69 if (!m)
cc377381 70 return r;
118ecf32 71
cc377381
LP
72 r = sd_bus_message_set_destination(m, sd->session->controller);
73 if (r < 0)
74 return r;
118ecf32
DH
75
76 switch (type) {
864fe630 77
118ecf32 78 case SESSION_DEVICE_RESUME:
cc377381
LP
79 r = sd_bus_message_append(m, "uuh", major, minor, sd->fd);
80 if (r < 0)
81 return r;
118ecf32 82 break;
864fe630 83
118ecf32
DH
84 case SESSION_DEVICE_TRY_PAUSE:
85 t = "pause";
86 break;
864fe630 87
118ecf32
DH
88 case SESSION_DEVICE_PAUSE:
89 t = "force";
90 break;
864fe630 91
118ecf32
DH
92 case SESSION_DEVICE_RELEASE:
93 t = "gone";
94 break;
864fe630 95
118ecf32 96 default:
cc377381 97 return -EINVAL;
118ecf32
DH
98 }
99
cc377381
LP
100 if (t) {
101 r = sd_bus_message_append(m, "uus", major, minor, t);
102 if (r < 0)
103 return r;
104 }
118ecf32 105
cc377381 106 return sd_bus_send(sd->session->manager->bus, m, NULL);
118ecf32
DH
107}
108
a3ddf73c 109static void sd_eviocrevoke(int fd) {
5d5330a8 110 static bool warned = false;
118ecf32
DH
111
112 assert(fd >= 0);
113
5d5330a8
LP
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");
118ecf32 118 warned = true;
118ecf32
DH
119 }
120 }
118ecf32
DH
121}
122
123static int sd_drmsetmaster(int fd) {
118ecf32
DH
124 assert(fd >= 0);
125
864fe630 126 if (ioctl(fd, DRM_IOCTL_SET_MASTER, 0) < 0)
118ecf32
DH
127 return -errno;
128
129 return 0;
130}
131
132static int sd_drmdropmaster(int fd) {
118ecf32
DH
133 assert(fd >= 0);
134
864fe630 135 if (ioctl(fd, DRM_IOCTL_DROP_MASTER, 0) < 0)
118ecf32
DH
136 return -errno;
137
138 return 0;
139}
140
141static int session_device_open(SessionDevice *sd, bool active) {
c2e5d024 142 int fd, r;
118ecf32 143
864fe630 144 assert(sd);
118ecf32 145 assert(sd->type != DEVICE_TYPE_UNKNOWN);
864fe630 146 assert(sd->node);
118ecf32
DH
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) {
864fe630 154
118ecf32 155 case DEVICE_TYPE_DRM:
c2e5d024 156 if (active) {
864fe630
LP
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. */
c2e5d024
DH
159 r = sd_drmsetmaster(fd);
160 if (r < 0) {
d1107170 161 close_nointr(fd);
c2e5d024
DH
162 return r;
163 }
864fe630
LP
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. */
4804600b 167 (void) sd_drmdropmaster(fd);
118ecf32 168 break;
864fe630 169
118ecf32
DH
170 case DEVICE_TYPE_EVDEV:
171 if (!active)
172 sd_eviocrevoke(fd);
173 break;
864fe630 174
118ecf32
DH
175 case DEVICE_TYPE_UNKNOWN:
176 default:
177 /* fallback for devices wihout synchronizations */
178 break;
179 }
180
181 return fd;
182}
183
184static 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) {
864fe630 194
118ecf32 195 case DEVICE_TYPE_DRM:
4d3900f1
LP
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 }
118ecf32 209 break;
864fe630 210
118ecf32 211 case DEVICE_TYPE_EVDEV:
864fe630 212 /* Evdev devices are revoked while inactive. Reopen it and we are fine. */
118ecf32
DH
213 r = session_device_open(sd, true);
214 if (r < 0)
215 return r;
864fe630
LP
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. */
aed24c4c 219 safe_close(sd->fd);
118ecf32
DH
220 sd->fd = r;
221 break;
864fe630 222
118ecf32
DH
223 case DEVICE_TYPE_UNKNOWN:
224 default:
340aff15 225 /* fallback for devices without synchronizations */
118ecf32
DH
226 break;
227 }
228
229 sd->active = true;
230 return 0;
231}
232
233static void session_device_stop(SessionDevice *sd) {
234 assert(sd);
235
236 if (!sd->active)
237 return;
238
239 switch (sd->type) {
340aff15 240
118ecf32
DH
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;
340aff15 248
118ecf32
DH
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;
340aff15 256
118ecf32
DH
257 case DEVICE_TYPE_UNKNOWN:
258 default:
259 /* fallback for devices without synchronization */
260 break;
261 }
262
263 sd->active = false;
264}
265
266static 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
3a83f522 274 if (streq_ptr(subsystem, "drm")) {
118ecf32
DH
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
285static 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);
3a83f522 315 } else if (sd->type != DEVICE_TYPE_DRM) {
118ecf32
DH
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;
353err_dev:
354 udev_device_unref(p ? : dev);
355 return r;
356}
357
aed24c4c 358int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out) {
118ecf32
DH
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
831dedef 381 r = hashmap_put(s->devices, &sd->dev, sd);
e38aa664 382 if (r < 0)
118ecf32 383 goto error;
118ecf32 384
aed24c4c
FB
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;
c2e5d024 400 }
aed24c4c 401 sd->fd = r;
c2e5d024 402 }
118ecf32 403
71fda00f 404 LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
118ecf32
DH
405
406 *out = sd;
407 return 0;
408
409error:
831dedef 410 hashmap_remove(s->devices, &sd->dev);
118ecf32
DH
411 free(sd->node);
412 free(sd);
413 return r;
414}
415
416void session_device_free(SessionDevice *sd) {
417 assert(sd);
418
1bef256c 419 /* Make sure to remove the pushed fd. */
4050e479 420 if (sd->pushed_fd) {
1bef256c
AJ
421 _cleanup_free_ char *m = NULL;
422 const char *id;
423 int r;
424
425 /* Session ID does not contain separators. */
426 id = sd->session->id;
427 assert(*(id + strcspn(id, "-\n")) == '\0');
428
429 r = asprintf(&m, "FDSTOREREMOVE=1\n"
430 "FDNAME=session-%s-device-%u-%u\n",
431 id, major(sd->dev), minor(sd->dev));
432 if (r >= 0)
433 (void) sd_notify(false, m);
4050e479
LP
434 }
435
118ecf32
DH
436 session_device_stop(sd);
437 session_device_notify(sd, SESSION_DEVICE_RELEASE);
4d219f53 438 safe_close(sd->fd);
118ecf32 439
71fda00f 440 LIST_REMOVE(sd_by_device, sd->device->session_devices, sd);
118ecf32 441
831dedef 442 hashmap_remove(sd->session->devices, &sd->dev);
118ecf32
DH
443
444 free(sd->node);
445 free(sd);
446}
447
448void session_device_complete_pause(SessionDevice *sd) {
d7bd01b5
DH
449 SessionDevice *iter;
450 Iterator i;
451
118ecf32
DH
452 if (!sd->active)
453 return;
454
455 session_device_stop(sd);
d7bd01b5
DH
456
457 /* if not all devices are paused, wait for further completion events */
458 HASHMAP_FOREACH(iter, sd->session->devices, i)
459 if (iter->active)
460 return;
461
462 /* complete any pending session switch */
463 seat_complete_switch(sd->session->seat);
118ecf32
DH
464}
465
466void session_device_resume_all(Session *s) {
467 SessionDevice *sd;
468 Iterator i;
118ecf32
DH
469
470 assert(s);
471
472 HASHMAP_FOREACH(sd, s->devices, i) {
d7ba71f4
LP
473 if (sd->active)
474 continue;
475
476 if (session_device_start(sd) < 0)
477 continue;
478 if (session_device_save(sd) < 0)
479 continue;
340aff15 480
d7ba71f4 481 session_device_notify(sd, SESSION_DEVICE_RESUME);
118ecf32
DH
482 }
483}
484
485void session_device_pause_all(Session *s) {
486 SessionDevice *sd;
487 Iterator i;
488
489 assert(s);
490
491 HASHMAP_FOREACH(sd, s->devices, i) {
d7ba71f4
LP
492 if (!sd->active)
493 continue;
494
495 session_device_stop(sd);
496 session_device_notify(sd, SESSION_DEVICE_PAUSE);
118ecf32
DH
497 }
498}
d7bd01b5
DH
499
500unsigned int session_device_try_pause_all(Session *s) {
d7ba71f4 501 unsigned num_pending = 0;
d7bd01b5
DH
502 SessionDevice *sd;
503 Iterator i;
d7bd01b5
DH
504
505 assert(s);
506
507 HASHMAP_FOREACH(sd, s->devices, i) {
d7ba71f4
LP
508 if (!sd->active)
509 continue;
510
511 session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE);
512 num_pending++;
d7bd01b5
DH
513 }
514
515 return num_pending;
516}
aed24c4c
FB
517
518int session_device_save(SessionDevice *sd) {
1bef256c
AJ
519 _cleanup_free_ char *m = NULL;
520 const char *id;
aed24c4c
FB
521 int r;
522
523 assert(sd);
524
4050e479
LP
525 /* Store device fd in PID1. It will send it back to us on restart so revocation will continue to work. To make
526 * things simple, send fds for all type of devices even if they don't support the revocation mechanism so we
527 * don't have to handle them differently later.
aed24c4c 528 *
4050e479
LP
529 * Note: for device supporting revocation, PID1 will drop a stored fd automatically if the corresponding device
530 * is revoked. */
531
532 if (sd->pushed_fd)
533 return 0;
1bef256c
AJ
534
535 /* Session ID does not contain separators. */
536 id = sd->session->id;
537 assert(*(id + strcspn(id, "-\n")) == '\0');
538
539 r = asprintf(&m, "FDSTORE=1\n"
540 "FDNAME=session-%s-device-%u-%u\n",
541 id, major(sd->dev), minor(sd->dev));
542 if (r < 0)
543 return r;
4050e479
LP
544
545 r = sd_pid_notify_with_fds(0, false, m, &sd->fd, 1);
aed24c4c 546 if (r < 0)
4050e479 547 return r;
aed24c4c 548
4050e479
LP
549 sd->pushed_fd = true;
550 return 1;
aed24c4c
FB
551}
552
553void session_device_attach_fd(SessionDevice *sd, int fd, bool active) {
4c9cb12c 554 assert(fd >= 0);
aed24c4c
FB
555 assert(sd);
556 assert(sd->fd < 0);
557 assert(!sd->active);
558
559 sd->fd = fd;
f8f9419e 560 sd->pushed_fd = true;
aed24c4c
FB
561 sd->active = active;
562}