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