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