chiark / gitweb /
logind: rework sd_eviocrevoke()
[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 int 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         return 0;
127 }
128
129 static int sd_drmsetmaster(int fd) {
130         assert(fd >= 0);
131
132         if (ioctl(fd, DRM_IOCTL_SET_MASTER, 0) < 0)
133                 return -errno;
134
135         return 0;
136 }
137
138 static int sd_drmdropmaster(int fd) {
139         assert(fd >= 0);
140
141         if (ioctl(fd, DRM_IOCTL_DROP_MASTER, 0) < 0)
142                 return -errno;
143
144         return 0;
145 }
146
147 static int session_device_open(SessionDevice *sd, bool active) {
148         int fd, r;
149
150         assert(sd);
151         assert(sd->type != DEVICE_TYPE_UNKNOWN);
152         assert(sd->node);
153
154         /* open device and try to get an udev_device from it */
155         fd = open(sd->node, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
156         if (fd < 0)
157                 return -errno;
158
159         switch (sd->type) {
160
161         case DEVICE_TYPE_DRM:
162                 if (active) {
163                         /* Weird legacy DRM semantics might return an error even though we're master. No way to detect
164                          * that so fail at all times and let caller retry in inactive state. */
165                         r = sd_drmsetmaster(fd);
166                         if (r < 0) {
167                                 close_nointr(fd);
168                                 return r;
169                         }
170                 } else
171                         /* DRM-Master is granted to the first user who opens a device automatically (ughh,
172                          * racy!). Hence, we just drop DRM-Master in case we were the first. */
173                         sd_drmdropmaster(fd);
174                 break;
175
176         case DEVICE_TYPE_EVDEV:
177                 if (!active)
178                         sd_eviocrevoke(fd);
179                 break;
180
181         case DEVICE_TYPE_UNKNOWN:
182         default:
183                 /* fallback for devices wihout synchronizations */
184                 break;
185         }
186
187         return fd;
188 }
189
190 static int session_device_start(SessionDevice *sd) {
191         int r;
192
193         assert(sd);
194         assert(session_is_active(sd->session));
195
196         if (sd->active)
197                 return 0;
198
199         switch (sd->type) {
200
201         case DEVICE_TYPE_DRM:
202                 /* Device is kept open. Simply call drmSetMaster() and hope there is no-one else. In case it fails, we
203                  * keep the device paused. Maybe at some point we have a drmStealMaster(). */
204                 r = sd_drmsetmaster(sd->fd);
205                 if (r < 0)
206                         return r;
207                 break;
208
209         case DEVICE_TYPE_EVDEV:
210                 /* Evdev devices are revoked while inactive. Reopen it and we are fine. */
211                 r = session_device_open(sd, true);
212                 if (r < 0)
213                         return r;
214
215                 /* For evdev devices, the file descriptor might be left uninitialized. This might happen while resuming
216                  * into a session and logind has been restarted right before. */
217                 safe_close(sd->fd);
218                 sd->fd = r;
219                 break;
220
221         case DEVICE_TYPE_UNKNOWN:
222         default:
223                 /* fallback for devices wihout synchronizations */
224                 break;
225         }
226
227         sd->active = true;
228         return 0;
229 }
230
231 static void session_device_stop(SessionDevice *sd) {
232         assert(sd);
233
234         if (!sd->active)
235                 return;
236
237         switch (sd->type) {
238         case DEVICE_TYPE_DRM:
239                 /* On DRM devices we simply drop DRM-Master but keep it open.
240                  * This allows the user to keep resources allocated. The
241                  * CAP_SYS_ADMIN restriction to DRM-Master prevents users from
242                  * circumventing this. */
243                 sd_drmdropmaster(sd->fd);
244                 break;
245         case DEVICE_TYPE_EVDEV:
246                 /* Revoke access on evdev file-descriptors during deactivation.
247                  * This will basically prevent any operations on the fd and
248                  * cannot be undone. Good side is: it needs no CAP_SYS_ADMIN
249                  * protection this way. */
250                 sd_eviocrevoke(sd->fd);
251                 break;
252         case DEVICE_TYPE_UNKNOWN:
253         default:
254                 /* fallback for devices without synchronization */
255                 break;
256         }
257
258         sd->active = false;
259 }
260
261 static DeviceType detect_device_type(struct udev_device *dev) {
262         const char *sysname, *subsystem;
263         DeviceType type;
264
265         sysname = udev_device_get_sysname(dev);
266         subsystem = udev_device_get_subsystem(dev);
267         type = DEVICE_TYPE_UNKNOWN;
268
269         if (streq_ptr(subsystem, "drm")) {
270                 if (startswith(sysname, "card"))
271                         type = DEVICE_TYPE_DRM;
272         } else if (streq_ptr(subsystem, "input")) {
273                 if (startswith(sysname, "event"))
274                         type = DEVICE_TYPE_EVDEV;
275         }
276
277         return type;
278 }
279
280 static int session_device_verify(SessionDevice *sd) {
281         struct udev_device *dev, *p = NULL;
282         const char *sp, *node;
283         int r;
284
285         dev = udev_device_new_from_devnum(sd->session->manager->udev, 'c', sd->dev);
286         if (!dev)
287                 return -ENODEV;
288
289         sp = udev_device_get_syspath(dev);
290         node = udev_device_get_devnode(dev);
291         if (!node) {
292                 r = -EINVAL;
293                 goto err_dev;
294         }
295
296         /* detect device type so we can find the correct sysfs parent */
297         sd->type = detect_device_type(dev);
298         if (sd->type == DEVICE_TYPE_UNKNOWN) {
299                 r = -ENODEV;
300                 goto err_dev;
301         } else if (sd->type == DEVICE_TYPE_EVDEV) {
302                 /* for evdev devices we need the parent node as device */
303                 p = dev;
304                 dev = udev_device_get_parent_with_subsystem_devtype(p, "input", NULL);
305                 if (!dev) {
306                         r = -ENODEV;
307                         goto err_dev;
308                 }
309                 sp = udev_device_get_syspath(dev);
310         } else if (sd->type != DEVICE_TYPE_DRM) {
311                 /* Prevent opening unsupported devices. Especially devices of
312                  * subsystem "input" must be opened via the evdev node as
313                  * we require EVIOCREVOKE. */
314                 r = -ENODEV;
315                 goto err_dev;
316         }
317
318         /* search for an existing seat device and return it if available */
319         sd->device = hashmap_get(sd->session->manager->devices, sp);
320         if (!sd->device) {
321                 /* The caller might have gotten the udev event before we were
322                  * able to process it. Hence, fake the "add" event and let the
323                  * logind-manager handle the new device. */
324                 r = manager_process_seat_device(sd->session->manager, dev);
325                 if (r < 0)
326                         goto err_dev;
327
328                 /* if it's still not available, then the device is invalid */
329                 sd->device = hashmap_get(sd->session->manager->devices, sp);
330                 if (!sd->device) {
331                         r = -ENODEV;
332                         goto err_dev;
333                 }
334         }
335
336         if (sd->device->seat != sd->session->seat) {
337                 r = -EPERM;
338                 goto err_dev;
339         }
340
341         sd->node = strdup(node);
342         if (!sd->node) {
343                 r = -ENOMEM;
344                 goto err_dev;
345         }
346
347         r = 0;
348 err_dev:
349         udev_device_unref(p ? : dev);
350         return r;
351 }
352
353 int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out) {
354         SessionDevice *sd;
355         int r;
356
357         assert(s);
358         assert(out);
359
360         if (!s->seat)
361                 return -EPERM;
362
363         sd = new0(SessionDevice, 1);
364         if (!sd)
365                 return -ENOMEM;
366
367         sd->session = s;
368         sd->dev = dev;
369         sd->fd = -1;
370         sd->type = DEVICE_TYPE_UNKNOWN;
371
372         r = session_device_verify(sd);
373         if (r < 0)
374                 goto error;
375
376         r = hashmap_put(s->devices, &sd->dev, sd);
377         if (r < 0) {
378                 r = -ENOMEM;
379                 goto error;
380         }
381
382         if (open_device) {
383                 /* Open the device for the first time. We need a valid fd to pass back
384                  * to the caller. If the session is not active, this _might_ immediately
385                  * revoke access and thus invalidate the fd. But this is still needed
386                  * to pass a valid fd back. */
387                 sd->active = session_is_active(s);
388                 r = session_device_open(sd, sd->active);
389                 if (r < 0) {
390                         /* EINVAL _may_ mean a master is active; retry inactive */
391                         if (sd->active && r == -EINVAL) {
392                                 sd->active = false;
393                                 r = session_device_open(sd, false);
394                         }
395                         if (r < 0)
396                                 goto error;
397                 }
398                 sd->fd = r;
399         }
400
401         LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
402
403         *out = sd;
404         return 0;
405
406 error:
407         hashmap_remove(s->devices, &sd->dev);
408         free(sd->node);
409         free(sd);
410         return r;
411 }
412
413 void session_device_free(SessionDevice *sd) {
414         assert(sd);
415
416         if (sd->pushed_fd) {
417                 const char *m;
418
419                 /* Remove the pushed fd again, just in case. */
420
421                 m = strjoina("FDSTOREREMOVE=1\n"
422                              "FDNAME=session-", sd->session->id);
423
424                 (void) sd_notify(false, m);
425         }
426
427         session_device_stop(sd);
428         session_device_notify(sd, SESSION_DEVICE_RELEASE);
429         close_nointr(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                         if (session_device_start(sd) < 0)
466                                 continue;
467                         if (session_device_save(sd) < 0)
468                                 continue;
469                         session_device_notify(sd, SESSION_DEVICE_RESUME);
470                 }
471         }
472 }
473
474 void session_device_pause_all(Session *s) {
475         SessionDevice *sd;
476         Iterator i;
477
478         assert(s);
479
480         HASHMAP_FOREACH(sd, s->devices, i) {
481                 if (sd->active) {
482                         session_device_stop(sd);
483                         session_device_notify(sd, SESSION_DEVICE_PAUSE);
484                 }
485         }
486 }
487
488 unsigned int session_device_try_pause_all(Session *s) {
489         SessionDevice *sd;
490         Iterator i;
491         unsigned int num_pending = 0;
492
493         assert(s);
494
495         HASHMAP_FOREACH(sd, s->devices, i) {
496                 if (sd->active) {
497                         session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE);
498                         ++num_pending;
499                 }
500         }
501
502         return num_pending;
503 }
504
505 int session_device_save(SessionDevice *sd) {
506         const char *m;
507         int r;
508
509         assert(sd);
510
511         /* Store device fd in PID1. It will send it back to us on restart so revocation will continue to work. To make
512          * things simple, send fds for all type of devices even if they don't support the revocation mechanism so we
513          * don't have to handle them differently later.
514          *
515          * Note: for device supporting revocation, PID1 will drop a stored fd automatically if the corresponding device
516          * is revoked. */
517
518         if (sd->pushed_fd)
519                 return 0;
520
521         m = strjoina("FDSTORE=1\n"
522                      "FDNAME=session", sd->session->id);
523
524         r = sd_pid_notify_with_fds(0, false, m, &sd->fd, 1);
525         if (r < 0)
526                 return r;
527
528         sd->pushed_fd = true;
529         return 1;
530 }
531
532 void session_device_attach_fd(SessionDevice *sd, int fd, bool active) {
533         assert(fd > 0);
534         assert(sd);
535         assert(sd->fd < 0);
536         assert(!sd->active);
537
538         sd->fd = fd;
539         sd->active = active;
540 }