chiark / gitweb /
tree-wide: beautify remaining copyright statements
[elogind.git] / src / login / logind-session-device.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3   Copyright © 2013 David Herrmann
4 ***/
5
6 #include <fcntl.h>
7 #include <linux/input.h>
8 #include <string.h>
9 #include <sys/ioctl.h>
10 #include <sys/types.h>
11
12 #if 0 /// elogind needs the systems udev header
13 #include "libudev.h"
14 #else
15 #include <libudev.h>
16 #endif // 0
17
18 #include "alloc-util.h"
19 #include "bus-util.h"
20 #include "fd-util.h"
21 #include "logind-session-device.h"
22 #include "missing.h"
23 #include "parse-util.h"
24 #include "sd-daemon.h"
25 #include "util.h"
26
27 enum SessionDeviceNotifications {
28         SESSION_DEVICE_RESUME,
29         SESSION_DEVICE_TRY_PAUSE,
30         SESSION_DEVICE_PAUSE,
31         SESSION_DEVICE_RELEASE,
32 };
33
34 static int session_device_notify(SessionDevice *sd, enum SessionDeviceNotifications type) {
35         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
36         _cleanup_free_ char *path = NULL;
37         const char *t = NULL;
38         uint32_t major, minor;
39         int r;
40
41         assert(sd);
42
43         major = major(sd->dev);
44         minor = minor(sd->dev);
45
46         if (!sd->session->controller)
47                 return 0;
48
49         path = session_bus_path(sd->session);
50         if (!path)
51                 return -ENOMEM;
52
53         r = sd_bus_message_new_signal(
54                         sd->session->manager->bus,
55                         &m, path,
56                         "org.freedesktop.login1.Session",
57                         (type == SESSION_DEVICE_RESUME) ? "ResumeDevice" : "PauseDevice");
58         if (!m)
59                 return r;
60
61         r = sd_bus_message_set_destination(m, sd->session->controller);
62         if (r < 0)
63                 return r;
64
65         switch (type) {
66
67         case SESSION_DEVICE_RESUME:
68                 r = sd_bus_message_append(m, "uuh", major, minor, sd->fd);
69                 if (r < 0)
70                         return r;
71                 break;
72
73         case SESSION_DEVICE_TRY_PAUSE:
74                 t = "pause";
75                 break;
76
77         case SESSION_DEVICE_PAUSE:
78                 t = "force";
79                 break;
80
81         case SESSION_DEVICE_RELEASE:
82                 t = "gone";
83                 break;
84
85         default:
86                 return -EINVAL;
87         }
88
89         if (t) {
90                 r = sd_bus_message_append(m, "uus", major, minor, t);
91                 if (r < 0)
92                         return r;
93         }
94
95         return sd_bus_send(sd->session->manager->bus, m, NULL);
96 }
97
98 static void sd_eviocrevoke(int fd) {
99         static bool warned = false;
100
101         assert(fd >= 0);
102
103         if (ioctl(fd, EVIOCREVOKE, NULL) < 0) {
104
105                 if (errno == EINVAL && !warned) {
106                         log_warning_errno(errno, "Kernel does not support evdev-revocation: %m");
107                         warned = true;
108                 }
109         }
110 }
111
112 static int sd_drmsetmaster(int fd) {
113         assert(fd >= 0);
114
115         if (ioctl(fd, DRM_IOCTL_SET_MASTER, 0) < 0)
116                 return -errno;
117
118         return 0;
119 }
120
121 static int sd_drmdropmaster(int fd) {
122         assert(fd >= 0);
123
124         if (ioctl(fd, DRM_IOCTL_DROP_MASTER, 0) < 0)
125                 return -errno;
126
127         return 0;
128 }
129
130 static int session_device_open(SessionDevice *sd, bool active) {
131         int fd, r;
132
133         assert(sd);
134         assert(sd->type != DEVICE_TYPE_UNKNOWN);
135         assert(sd->node);
136
137         /* open device and try to get an udev_device from it */
138         fd = open(sd->node, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
139         if (fd < 0)
140                 return -errno;
141
142         switch (sd->type) {
143
144         case DEVICE_TYPE_DRM:
145                 if (active) {
146                         /* Weird legacy DRM semantics might return an error even though we're master. No way to detect
147                          * that so fail at all times and let caller retry in inactive state. */
148                         r = sd_drmsetmaster(fd);
149                         if (r < 0) {
150                                 close_nointr(fd);
151                                 return r;
152                         }
153                 } else
154                         /* DRM-Master is granted to the first user who opens a device automatically (ughh,
155                          * racy!). Hence, we just drop DRM-Master in case we were the first. */
156                         (void) sd_drmdropmaster(fd);
157                 break;
158
159         case DEVICE_TYPE_EVDEV:
160                 if (!active)
161                         sd_eviocrevoke(fd);
162                 break;
163
164         case DEVICE_TYPE_UNKNOWN:
165         default:
166                 /* fallback for devices wihout synchronizations */
167                 break;
168         }
169
170         return fd;
171 }
172
173 static int session_device_start(SessionDevice *sd) {
174         int r;
175
176         assert(sd);
177         assert(session_is_active(sd->session));
178
179         if (sd->active)
180                 return 0;
181
182         switch (sd->type) {
183
184         case DEVICE_TYPE_DRM:
185                 if (sd->fd < 0) {
186                         log_error("Failed to re-activate DRM fd, as the fd was lost (maybe logind restart went wrong?)");
187                         return -EBADF;
188                 }
189
190                 /* Device is kept open. Simply call drmSetMaster() and hope there is no-one else. In case it fails, we
191                  * keep the device paused. Maybe at some point we have a drmStealMaster(). */
192                 r = sd_drmsetmaster(sd->fd);
193                 if (r < 0)
194                         return r;
195                 break;
196
197         case DEVICE_TYPE_EVDEV:
198                 /* Evdev devices are revoked while inactive. Reopen it and we are fine. */
199                 r = session_device_open(sd, true);
200                 if (r < 0)
201                         return r;
202
203                 /* For evdev devices, the file descriptor might be left uninitialized. This might happen while resuming
204                  * into a session and logind has been restarted right before. */
205                 safe_close(sd->fd);
206                 sd->fd = r;
207                 break;
208
209         case DEVICE_TYPE_UNKNOWN:
210         default:
211                 /* fallback for devices without synchronizations */
212                 break;
213         }
214
215         sd->active = true;
216         return 0;
217 }
218
219 static void session_device_stop(SessionDevice *sd) {
220         assert(sd);
221
222         if (!sd->active)
223                 return;
224
225         switch (sd->type) {
226
227         case DEVICE_TYPE_DRM:
228                 if (sd->fd < 0) {
229                         log_error("Failed to de-activate DRM fd, as the fd was lost (maybe logind restart went wrong?)");
230                         return;
231                 }
232
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
240         case DEVICE_TYPE_EVDEV:
241                 /* Revoke access on evdev file-descriptors during deactivation.
242                  * This will basically prevent any operations on the fd and
243                  * cannot be undone. Good side is: it needs no CAP_SYS_ADMIN
244                  * protection this way. */
245                 sd_eviocrevoke(sd->fd);
246                 break;
247
248         case DEVICE_TYPE_UNKNOWN:
249         default:
250                 /* fallback for devices without synchronization */
251                 break;
252         }
253
254         sd->active = false;
255 }
256
257 static 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
265         if (streq_ptr(subsystem, "drm")) {
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
276 static 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);
306         } else if (sd->type != DEVICE_TYPE_DRM) {
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;
344 err_dev:
345         udev_device_unref(p ? : dev);
346         return r;
347 }
348
349 int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out) {
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
372         r = hashmap_put(s->devices, &sd->dev, sd);
373         if (r < 0)
374                 goto error;
375
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;
391                 }
392                 sd->fd = r;
393         }
394
395         LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
396
397         *out = sd;
398         return 0;
399
400 error:
401         hashmap_remove(s->devices, &sd->dev);
402         free(sd->node);
403         free(sd);
404         return r;
405 }
406
407 void session_device_free(SessionDevice *sd) {
408         assert(sd);
409
410         /* Make sure to remove the pushed fd. */
411         if (sd->pushed_fd) {
412                 _cleanup_free_ char *m = NULL;
413                 const char *id;
414                 int r;
415
416                 /* Session ID does not contain separators. */
417                 id = sd->session->id;
418                 assert(*(id + strcspn(id, "-\n")) == '\0');
419
420                 r = asprintf(&m, "FDSTOREREMOVE=1\n"
421                                  "FDNAME=session-%s-device-%u-%u\n",
422                                  id, major(sd->dev), minor(sd->dev));
423                 if (r >= 0)
424                         (void) sd_notify(false, m);
425         }
426
427         session_device_stop(sd);
428         session_device_notify(sd, SESSION_DEVICE_RELEASE);
429         safe_close(sd->fd);
430
431         LIST_REMOVE(sd_by_device, sd->device->session_devices, sd);
432
433         hashmap_remove(sd->session->devices, &sd->dev);
434
435         free(sd->node);
436         free(sd);
437 }
438
439 void session_device_complete_pause(SessionDevice *sd) {
440         SessionDevice *iter;
441         Iterator i;
442
443         if (!sd->active)
444                 return;
445
446         session_device_stop(sd);
447
448         /* if not all devices are paused, wait for further completion events */
449         HASHMAP_FOREACH(iter, sd->session->devices, i)
450                 if (iter->active)
451                         return;
452
453         /* complete any pending session switch */
454         seat_complete_switch(sd->session->seat);
455 }
456
457 void session_device_resume_all(Session *s) {
458         SessionDevice *sd;
459         Iterator i;
460
461         assert(s);
462
463         HASHMAP_FOREACH(sd, s->devices, i) {
464                 if (sd->active)
465                         continue;
466
467                 if (session_device_start(sd) < 0)
468                         continue;
469                 if (session_device_save(sd) < 0)
470                         continue;
471
472                 session_device_notify(sd, SESSION_DEVICE_RESUME);
473         }
474 }
475
476 void session_device_pause_all(Session *s) {
477         SessionDevice *sd;
478         Iterator i;
479
480         assert(s);
481
482         HASHMAP_FOREACH(sd, s->devices, i) {
483                 if (!sd->active)
484                         continue;
485
486                 session_device_stop(sd);
487                 session_device_notify(sd, SESSION_DEVICE_PAUSE);
488         }
489 }
490
491 unsigned int session_device_try_pause_all(Session *s) {
492         unsigned num_pending = 0;
493         SessionDevice *sd;
494         Iterator i;
495
496         assert(s);
497
498         HASHMAP_FOREACH(sd, s->devices, i) {
499                 if (!sd->active)
500                         continue;
501
502                 session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE);
503                 num_pending++;
504         }
505
506         return num_pending;
507 }
508
509 int session_device_save(SessionDevice *sd) {
510         _cleanup_free_ char *m = NULL;
511         const char *id;
512         int r;
513
514         assert(sd);
515
516         /* Store device fd in PID1. It will send it back to us on restart so revocation will continue to work. To make
517          * things simple, send fds for all type of devices even if they don't support the revocation mechanism so we
518          * don't have to handle them differently later.
519          *
520          * Note: for device supporting revocation, PID1 will drop a stored fd automatically if the corresponding device
521          * is revoked. */
522
523         if (sd->pushed_fd)
524                 return 0;
525
526         /* Session ID does not contain separators. */
527         id = sd->session->id;
528         assert(*(id + strcspn(id, "-\n")) == '\0');
529
530         r = asprintf(&m, "FDSTORE=1\n"
531                          "FDNAME=session-%s-device-%u-%u\n",
532                          id, major(sd->dev), minor(sd->dev));
533         if (r < 0)
534                 return r;
535
536         r = sd_pid_notify_with_fds(0, false, m, &sd->fd, 1);
537         if (r < 0)
538                 return r;
539
540         sd->pushed_fd = true;
541         return 1;
542 }
543
544 void session_device_attach_fd(SessionDevice *sd, int fd, bool active) {
545         assert(fd >= 0);
546         assert(sd);
547         assert(sd->fd < 0);
548         assert(!sd->active);
549
550         sd->fd = fd;
551         sd->pushed_fd = true;
552         sd->active = active;
553 }