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