]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/logind-session-device.c
Add SPDX license identifiers to source files under the LGPL
[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) {
77 case SESSION_DEVICE_RESUME:
cc377381
LP
78 r = sd_bus_message_append(m, "uuh", major, minor, sd->fd);
79 if (r < 0)
80 return r;
118ecf32
DH
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:
cc377381 92 return -EINVAL;
118ecf32
DH
93 }
94
cc377381
LP
95 if (t) {
96 r = sd_bus_message_append(m, "uus", major, minor, t);
97 if (r < 0)
98 return r;
99 }
118ecf32 100
cc377381 101 return sd_bus_send(sd->session->manager->bus, m, NULL);
118ecf32
DH
102}
103
104static int sd_eviocrevoke(int fd) {
105 static bool warned;
106 int r;
107
108 assert(fd >= 0);
109
8dbce34b 110 r = ioctl(fd, EVIOCREVOKE, NULL);
118ecf32
DH
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
122static 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
134static 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
146static int session_device_open(SessionDevice *sd, bool active) {
c2e5d024 147 int fd, r;
118ecf32
DH
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:
c2e5d024
DH
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) {
d1107170 165 close_nointr(fd);
c2e5d024
DH
166 return r;
167 }
168 } else {
118ecf32
DH
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;
118ecf32
DH
179 case DEVICE_TYPE_UNKNOWN:
180 default:
181 /* fallback for devices wihout synchronizations */
182 break;
183 }
184
185 return fd;
186}
187
188static 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;
aed24c4c
FB
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);
118ecf32
DH
216 sd->fd = r;
217 break;
118ecf32
DH
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
228static 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;
118ecf32
DH
249 case DEVICE_TYPE_UNKNOWN:
250 default:
251 /* fallback for devices without synchronization */
252 break;
253 }
254
255 sd->active = false;
256}
257
258static 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
3a83f522 266 if (streq_ptr(subsystem, "drm")) {
118ecf32
DH
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
277static 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);
3a83f522 307 } else if (sd->type != DEVICE_TYPE_DRM) {
118ecf32
DH
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;
345err_dev:
346 udev_device_unref(p ? : dev);
347 return r;
348}
349
aed24c4c 350int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out) {
118ecf32
DH
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
831dedef 373 r = hashmap_put(s->devices, &sd->dev, sd);
118ecf32
DH
374 if (r < 0) {
375 r = -ENOMEM;
376 goto error;
377 }
378
aed24c4c
FB
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;
c2e5d024 394 }
aed24c4c 395 sd->fd = r;
c2e5d024 396 }
118ecf32 397
71fda00f 398 LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
118ecf32
DH
399
400 *out = sd;
401 return 0;
402
403error:
831dedef 404 hashmap_remove(s->devices, &sd->dev);
118ecf32
DH
405 free(sd->node);
406 free(sd);
407 return r;
408}
409
410void session_device_free(SessionDevice *sd) {
411 assert(sd);
412
413 session_device_stop(sd);
414 session_device_notify(sd, SESSION_DEVICE_RELEASE);
d1107170 415 close_nointr(sd->fd);
118ecf32 416
71fda00f 417 LIST_REMOVE(sd_by_device, sd->device->session_devices, sd);
118ecf32 418
831dedef 419 hashmap_remove(sd->session->devices, &sd->dev);
118ecf32
DH
420
421 free(sd->node);
422 free(sd);
423}
424
425void session_device_complete_pause(SessionDevice *sd) {
d7bd01b5
DH
426 SessionDevice *iter;
427 Iterator i;
428
118ecf32
DH
429 if (!sd->active)
430 return;
431
432 session_device_stop(sd);
d7bd01b5
DH
433
434 /* if not all devices are paused, wait for further completion events */
435 HASHMAP_FOREACH(iter, sd->session->devices, i)
436 if (iter->active)
437 return;
438
439 /* complete any pending session switch */
440 seat_complete_switch(sd->session->seat);
118ecf32
DH
441}
442
443void session_device_resume_all(Session *s) {
444 SessionDevice *sd;
445 Iterator i;
118ecf32
DH
446
447 assert(s);
448
449 HASHMAP_FOREACH(sd, s->devices, i) {
450 if (!sd->active) {
aed24c4c
FB
451 if (session_device_start(sd) < 0)
452 continue;
453 if (session_device_save(sd) < 0)
454 continue;
455 session_device_notify(sd, SESSION_DEVICE_RESUME);
118ecf32
DH
456 }
457 }
458}
459
460void session_device_pause_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 session_device_stop(sd);
469 session_device_notify(sd, SESSION_DEVICE_PAUSE);
470 }
471 }
472}
d7bd01b5
DH
473
474unsigned int session_device_try_pause_all(Session *s) {
475 SessionDevice *sd;
476 Iterator i;
477 unsigned int num_pending = 0;
478
479 assert(s);
480
481 HASHMAP_FOREACH(sd, s->devices, i) {
482 if (sd->active) {
483 session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE);
484 ++num_pending;
485 }
486 }
487
488 return num_pending;
489}
aed24c4c
FB
490
491int session_device_save(SessionDevice *sd) {
492 _cleanup_free_ char *state = NULL;
493 int r;
494
495 assert(sd);
496
497 /* Store device fd in PID1. It will send it back to us on
498 * restart so revocation will continue to work. To make things
499 * simple, send fds for all type of devices even if they don't
500 * support the revocation mechanism so we don't have to handle
501 * them differently later.
502 *
503 * Note: for device supporting revocation, PID1 will drop a
504 * stored fd automatically if the corresponding device is
505 * revoked. */
506 r = asprintf(&state, "FDSTORE=1\n"
507 "FDNAME=session-%s", sd->session->id);
508 if (r < 0)
509 return -ENOMEM;
510
511 return sd_pid_notify_with_fds(0, false, state, &sd->fd, 1);
512}
513
514void session_device_attach_fd(SessionDevice *sd, int fd, bool active) {
515 assert(fd > 0);
516 assert(sd);
517 assert(sd->fd < 0);
518 assert(!sd->active);
519
520 sd->fd = fd;
521 sd->active = active;
522}