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