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