chiark / gitweb /
ca0c512a1aae79837f4aa90a13987ffb5fe7c5e6
[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 wihout 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         case DEVICE_TYPE_DRM:
245                 /* On DRM devices we simply drop DRM-Master but keep it open.
246                  * This allows the user to keep resources allocated. The
247                  * CAP_SYS_ADMIN restriction to DRM-Master prevents users from
248                  * circumventing this. */
249                 sd_drmdropmaster(sd->fd);
250                 break;
251         case DEVICE_TYPE_EVDEV:
252                 /* Revoke access on evdev file-descriptors during deactivation.
253                  * This will basically prevent any operations on the fd and
254                  * cannot be undone. Good side is: it needs no CAP_SYS_ADMIN
255                  * protection this way. */
256                 sd_eviocrevoke(sd->fd);
257                 break;
258         case DEVICE_TYPE_UNKNOWN:
259         default:
260                 /* fallback for devices without synchronization */
261                 break;
262         }
263
264         sd->active = false;
265 }
266
267 static DeviceType detect_device_type(struct udev_device *dev) {
268         const char *sysname, *subsystem;
269         DeviceType type;
270
271         sysname = udev_device_get_sysname(dev);
272         subsystem = udev_device_get_subsystem(dev);
273         type = DEVICE_TYPE_UNKNOWN;
274
275         if (streq_ptr(subsystem, "drm")) {
276                 if (startswith(sysname, "card"))
277                         type = DEVICE_TYPE_DRM;
278         } else if (streq_ptr(subsystem, "input")) {
279                 if (startswith(sysname, "event"))
280                         type = DEVICE_TYPE_EVDEV;
281         }
282
283         return type;
284 }
285
286 static int session_device_verify(SessionDevice *sd) {
287         struct udev_device *dev, *p = NULL;
288         const char *sp, *node;
289         int r;
290
291         dev = udev_device_new_from_devnum(sd->session->manager->udev, 'c', sd->dev);
292         if (!dev)
293                 return -ENODEV;
294
295         sp = udev_device_get_syspath(dev);
296         node = udev_device_get_devnode(dev);
297         if (!node) {
298                 r = -EINVAL;
299                 goto err_dev;
300         }
301
302         /* detect device type so we can find the correct sysfs parent */
303         sd->type = detect_device_type(dev);
304         if (sd->type == DEVICE_TYPE_UNKNOWN) {
305                 r = -ENODEV;
306                 goto err_dev;
307         } else if (sd->type == DEVICE_TYPE_EVDEV) {
308                 /* for evdev devices we need the parent node as device */
309                 p = dev;
310                 dev = udev_device_get_parent_with_subsystem_devtype(p, "input", NULL);
311                 if (!dev) {
312                         r = -ENODEV;
313                         goto err_dev;
314                 }
315                 sp = udev_device_get_syspath(dev);
316         } else if (sd->type != DEVICE_TYPE_DRM) {
317                 /* Prevent opening unsupported devices. Especially devices of
318                  * subsystem "input" must be opened via the evdev node as
319                  * we require EVIOCREVOKE. */
320                 r = -ENODEV;
321                 goto err_dev;
322         }
323
324         /* search for an existing seat device and return it if available */
325         sd->device = hashmap_get(sd->session->manager->devices, sp);
326         if (!sd->device) {
327                 /* The caller might have gotten the udev event before we were
328                  * able to process it. Hence, fake the "add" event and let the
329                  * logind-manager handle the new device. */
330                 r = manager_process_seat_device(sd->session->manager, dev);
331                 if (r < 0)
332                         goto err_dev;
333
334                 /* if it's still not available, then the device is invalid */
335                 sd->device = hashmap_get(sd->session->manager->devices, sp);
336                 if (!sd->device) {
337                         r = -ENODEV;
338                         goto err_dev;
339                 }
340         }
341
342         if (sd->device->seat != sd->session->seat) {
343                 r = -EPERM;
344                 goto err_dev;
345         }
346
347         sd->node = strdup(node);
348         if (!sd->node) {
349                 r = -ENOMEM;
350                 goto err_dev;
351         }
352
353         r = 0;
354 err_dev:
355         udev_device_unref(p ? : dev);
356         return r;
357 }
358
359 int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out) {
360         SessionDevice *sd;
361         int r;
362
363         assert(s);
364         assert(out);
365
366         if (!s->seat)
367                 return -EPERM;
368
369         sd = new0(SessionDevice, 1);
370         if (!sd)
371                 return -ENOMEM;
372
373         sd->session = s;
374         sd->dev = dev;
375         sd->fd = -1;
376         sd->type = DEVICE_TYPE_UNKNOWN;
377
378         r = session_device_verify(sd);
379         if (r < 0)
380                 goto error;
381
382         r = hashmap_put(s->devices, &sd->dev, sd);
383         if (r < 0)
384                 goto error;
385
386         if (open_device) {
387                 /* Open the device for the first time. We need a valid fd to pass back
388                  * to the caller. If the session is not active, this _might_ immediately
389                  * revoke access and thus invalidate the fd. But this is still needed
390                  * to pass a valid fd back. */
391                 sd->active = session_is_active(s);
392                 r = session_device_open(sd, sd->active);
393                 if (r < 0) {
394                         /* EINVAL _may_ mean a master is active; retry inactive */
395                         if (sd->active && r == -EINVAL) {
396                                 sd->active = false;
397                                 r = session_device_open(sd, false);
398                         }
399                         if (r < 0)
400                                 goto error;
401                 }
402                 sd->fd = r;
403         }
404
405         LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
406
407         *out = sd;
408         return 0;
409
410 error:
411         hashmap_remove(s->devices, &sd->dev);
412         free(sd->node);
413         free(sd);
414         return r;
415 }
416
417 void session_device_free(SessionDevice *sd) {
418         assert(sd);
419
420         if (sd->pushed_fd) {
421                 const char *m;
422
423                 /* Remove the pushed fd again, just in case. */
424
425                 m = strjoina("FDSTOREREMOVE=1\n"
426                              "FDNAME=session-", sd->session->id);
427
428                 (void) sd_notify(false, m);
429         }
430
431         session_device_stop(sd);
432         session_device_notify(sd, SESSION_DEVICE_RELEASE);
433         safe_close(sd->fd);
434
435         LIST_REMOVE(sd_by_device, sd->device->session_devices, sd);
436
437         hashmap_remove(sd->session->devices, &sd->dev);
438
439         free(sd->node);
440         free(sd);
441 }
442
443 void session_device_complete_pause(SessionDevice *sd) {
444         SessionDevice *iter;
445         Iterator i;
446
447         if (!sd->active)
448                 return;
449
450         session_device_stop(sd);
451
452         /* if not all devices are paused, wait for further completion events */
453         HASHMAP_FOREACH(iter, sd->session->devices, i)
454                 if (iter->active)
455                         return;
456
457         /* complete any pending session switch */
458         seat_complete_switch(sd->session->seat);
459 }
460
461 void session_device_resume_all(Session *s) {
462         SessionDevice *sd;
463         Iterator i;
464
465         assert(s);
466
467         HASHMAP_FOREACH(sd, s->devices, i) {
468                 if (sd->active)
469                         continue;
470
471                 if (session_device_start(sd) < 0)
472                         continue;
473                 if (session_device_save(sd) < 0)
474                         continue;
475                 session_device_notify(sd, SESSION_DEVICE_RESUME);
476         }
477 }
478
479 void session_device_pause_all(Session *s) {
480         SessionDevice *sd;
481         Iterator i;
482
483         assert(s);
484
485         HASHMAP_FOREACH(sd, s->devices, i) {
486                 if (!sd->active)
487                         continue;
488
489                 session_device_stop(sd);
490                 session_device_notify(sd, SESSION_DEVICE_PAUSE);
491         }
492 }
493
494 unsigned int session_device_try_pause_all(Session *s) {
495         unsigned num_pending = 0;
496         SessionDevice *sd;
497         Iterator i;
498
499         assert(s);
500
501         HASHMAP_FOREACH(sd, s->devices, i) {
502                 if (!sd->active)
503                         continue;
504
505                 session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE);
506                 num_pending++;
507         }
508
509         return num_pending;
510 }
511
512 int session_device_save(SessionDevice *sd) {
513         const char *m;
514         int r;
515
516         assert(sd);
517
518         /* Store device fd in PID1. It will send it back to us on restart so revocation will continue to work. To make
519          * things simple, send fds for all type of devices even if they don't support the revocation mechanism so we
520          * don't have to handle them differently later.
521          *
522          * Note: for device supporting revocation, PID1 will drop a stored fd automatically if the corresponding device
523          * is revoked. */
524
525         if (sd->pushed_fd)
526                 return 0;
527
528         m = strjoina("FDSTORE=1\n"
529                      "FDNAME=session", sd->session->id);
530
531         r = sd_pid_notify_with_fds(0, false, m, &sd->fd, 1);
532         if (r < 0)
533                 return r;
534
535         sd->pushed_fd = true;
536         return 1;
537 }
538
539 void session_device_attach_fd(SessionDevice *sd, int fd, bool active) {
540         assert(fd >= 0);
541         assert(sd);
542         assert(sd->fd < 0);
543         assert(!sd->active);
544
545         sd->fd = fd;
546         sd->active = active;
547 }