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