chiark / gitweb /
546c53710e08ba9b857028dd926e9110e572023b
[elogind.git] / src / login / logind-session-device.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2013 David Herrmann
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <assert.h>
23 #include <fcntl.h>
24 #include <libudev.h>
25 #include <linux/input.h>
26 #include <linux/ioctl.h>
27 #include <string.h>
28 #include <sys/ioctl.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31 #include <unistd.h>
32
33 #include "dbus-common.h"
34 #include "logind-session-device.h"
35 #include "util.h"
36 #include "missing.h"
37
38 enum SessionDeviceNotifications {
39         SESSION_DEVICE_RESUME,
40         SESSION_DEVICE_TRY_PAUSE,
41         SESSION_DEVICE_PAUSE,
42         SESSION_DEVICE_RELEASE,
43 };
44
45 static void session_device_notify(SessionDevice *sd, enum SessionDeviceNotifications type) {
46         _cleanup_dbus_message_unref_ DBusMessage *m = NULL;
47         _cleanup_free_ char *path = NULL;
48         const char *t = NULL;
49         uint32_t major, minor;
50
51         assert(sd);
52
53         major = major(sd->dev);
54         minor = minor(sd->dev);
55
56         if (!sd->session->controller)
57                 return;
58
59         path = session_bus_path(sd->session);
60         if (!path)
61                 return;
62
63         m = dbus_message_new_signal(path,
64                                     "org.freedesktop.login1.Session",
65                                     (type == SESSION_DEVICE_RESUME) ? "ResumeDevice" : "PauseDevice");
66         if (!m)
67                 return;
68
69         if (!dbus_message_set_destination(m, sd->session->controller))
70                 return;
71
72         switch (type) {
73         case SESSION_DEVICE_RESUME:
74                 if (!dbus_message_append_args(m,
75                                               DBUS_TYPE_UINT32, &major,
76                                               DBUS_TYPE_UINT32, &minor,
77                                               DBUS_TYPE_UNIX_FD, &sd->fd,
78                                               DBUS_TYPE_INVALID))
79                         return;
80                 break;
81         case SESSION_DEVICE_TRY_PAUSE:
82                 t = "pause";
83                 break;
84         case SESSION_DEVICE_PAUSE:
85                 t = "force";
86                 break;
87         case SESSION_DEVICE_RELEASE:
88                 t = "gone";
89                 break;
90         default:
91                 return;
92         }
93
94         if (t && !dbus_message_append_args(m,
95                                            DBUS_TYPE_UINT32, &major,
96                                            DBUS_TYPE_UINT32, &minor,
97                                            DBUS_TYPE_STRING, &t,
98                                            DBUS_TYPE_INVALID))
99                 return;
100
101         dbus_connection_send(sd->session->manager->bus, m, NULL);
102 }
103
104 static int sd_eviocrevoke(int fd) {
105         static bool warned;
106         int r;
107
108         assert(fd >= 0);
109
110         r = ioctl(fd, EVIOCREVOKE, 1);
111         if (r < 0) {
112                 r = -errno;
113                 if (r == -EINVAL && !warned) {
114                         warned = true;
115                         log_warning("kernel does not support evdev-revocation");
116                 }
117         }
118
119         return 0;
120 }
121
122 static int sd_drmsetmaster(int fd) {
123         int r;
124
125         assert(fd >= 0);
126
127         r = ioctl(fd, DRM_IOCTL_SET_MASTER, 0);
128         if (r < 0)
129                 return -errno;
130
131         return 0;
132 }
133
134 static int sd_drmdropmaster(int fd) {
135         int r;
136
137         assert(fd >= 0);
138
139         r = ioctl(fd, DRM_IOCTL_DROP_MASTER, 0);
140         if (r < 0)
141                 return -errno;
142
143         return 0;
144 }
145
146 static int session_device_open(SessionDevice *sd, bool active) {
147         int fd, r;
148
149         assert(sd->type != DEVICE_TYPE_UNKNOWN);
150
151         /* open device and try to get an udev_device from it */
152         fd = open(sd->node, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
153         if (fd < 0)
154                 return -errno;
155
156         switch (sd->type) {
157         case DEVICE_TYPE_DRM:
158                 if (active) {
159                         /* Weird legacy DRM semantics might return an error
160                          * even though we're master. No way to detect that so
161                          * fail at all times and let caller retry in inactive
162                          * state. */
163                         r = sd_drmsetmaster(fd);
164                         if (r < 0) {
165                                 close(fd);
166                                 return r;
167                         }
168                 } else {
169                         /* DRM-Master is granted to the first user who opens a
170                          * device automatically (ughh, racy!). Hence, we just
171                          * drop DRM-Master in case we were the first. */
172                         sd_drmdropmaster(fd);
173                 }
174                 break;
175         case DEVICE_TYPE_EVDEV:
176                 if (!active)
177                         sd_eviocrevoke(fd);
178                 break;
179         case DEVICE_TYPE_FBDEV:
180         case DEVICE_TYPE_UNKNOWN:
181         default:
182                 /* fallback for devices wihout synchronizations */
183                 break;
184         }
185
186         return fd;
187 }
188
189 static int session_device_start(SessionDevice *sd) {
190         int r;
191
192         assert(sd);
193         assert(session_is_active(sd->session));
194
195         if (sd->active)
196                 return 0;
197
198         switch (sd->type) {
199         case DEVICE_TYPE_DRM:
200                 /* Device is kept open. Simply call drmSetMaster() and hope
201                  * there is no-one else. In case it fails, we keep the device
202                  * paused. Maybe at some point we have a drmStealMaster(). */
203                 r = sd_drmsetmaster(sd->fd);
204                 if (r < 0)
205                         return r;
206                 break;
207         case DEVICE_TYPE_EVDEV:
208                 /* Evdev devices are revoked while inactive. Reopen it and we
209                  * are fine. */
210                 r = session_device_open(sd, true);
211                 if (r < 0)
212                         return r;
213                 close_nointr_nofail(sd->fd);
214                 sd->fd = r;
215                 break;
216         case DEVICE_TYPE_FBDEV:
217                 /* fbdev devices have no way to synchronize access. Moreover,
218                  * they mostly operate through mmaps() without any pageflips
219                  * and modesetting, so there is no way for us to prevent access
220                  * but tear down mmaps.
221                  * That would be quite expensive to do on a per-fd context. So
222                  * ignore legcy fbdev and let its users feel the pain they asked
223                  * for when deciding for fbdev. */
224         case DEVICE_TYPE_UNKNOWN:
225         default:
226                 /* fallback for devices wihout synchronizations */
227                 break;
228         }
229
230         sd->active = true;
231         return 0;
232 }
233
234 static void session_device_stop(SessionDevice *sd) {
235         assert(sd);
236
237         if (!sd->active)
238                 return;
239
240         switch (sd->type) {
241         case DEVICE_TYPE_DRM:
242                 /* On DRM devices we simply drop DRM-Master but keep it open.
243                  * This allows the user to keep resources allocated. The
244                  * CAP_SYS_ADMIN restriction to DRM-Master prevents users from
245                  * circumventing this. */
246                 sd_drmdropmaster(sd->fd);
247                 break;
248         case DEVICE_TYPE_EVDEV:
249                 /* Revoke access on evdev file-descriptors during deactivation.
250                  * This will basically prevent any operations on the fd and
251                  * cannot be undone. Good side is: it needs no CAP_SYS_ADMIN
252                  * protection this way. */
253                 sd_eviocrevoke(sd->fd);
254                 break;
255         case DEVICE_TYPE_FBDEV:
256         case DEVICE_TYPE_UNKNOWN:
257         default:
258                 /* fallback for devices without synchronization */
259                 break;
260         }
261
262         sd->active = false;
263 }
264
265 static DeviceType detect_device_type(struct udev_device *dev) {
266         const char *sysname, *subsystem;
267         DeviceType type;
268
269         sysname = udev_device_get_sysname(dev);
270         subsystem = udev_device_get_subsystem(dev);
271         type = DEVICE_TYPE_UNKNOWN;
272
273         if (streq_ptr(subsystem, "graphics")) {
274                 if (!streq(sysname, "fbcon") && startswith(sysname, "fb"))
275                         type = DEVICE_TYPE_FBDEV;
276         } else if (streq_ptr(subsystem, "drm")) {
277                 if (startswith(sysname, "card"))
278                         type = DEVICE_TYPE_DRM;
279         } else if (streq_ptr(subsystem, "input")) {
280                 if (startswith(sysname, "event"))
281                         type = DEVICE_TYPE_EVDEV;
282         }
283
284         return type;
285 }
286
287 static int session_device_verify(SessionDevice *sd) {
288         struct udev_device *dev, *p = NULL;
289         const char *sp, *node;
290         int r;
291
292         dev = udev_device_new_from_devnum(sd->session->manager->udev, 'c', sd->dev);
293         if (!dev)
294                 return -ENODEV;
295
296         sp = udev_device_get_syspath(dev);
297         node = udev_device_get_devnode(dev);
298         if (!node) {
299                 r = -EINVAL;
300                 goto err_dev;
301         }
302
303         /* detect device type so we can find the correct sysfs parent */
304         sd->type = detect_device_type(dev);
305         if (sd->type == DEVICE_TYPE_UNKNOWN) {
306                 r = -ENODEV;
307                 goto err_dev;
308         } else if (sd->type == DEVICE_TYPE_EVDEV) {
309                 /* for evdev devices we need the parent node as device */
310                 p = dev;
311                 dev = udev_device_get_parent_with_subsystem_devtype(p, "input", NULL);
312                 if (!dev) {
313                         r = -ENODEV;
314                         goto err_dev;
315                 }
316                 sp = udev_device_get_syspath(dev);
317         } else if (sd->type != DEVICE_TYPE_FBDEV &&
318                    sd->type != DEVICE_TYPE_DRM) {
319                 /* Prevent opening unsupported devices. Especially devices of
320                  * subsystem "input" must be opened via the evdev node as
321                  * we require EVIOCREVOKE. */
322                 r = -ENODEV;
323                 goto err_dev;
324         }
325
326         /* search for an existing seat device and return it if available */
327         sd->device = hashmap_get(sd->session->manager->devices, sp);
328         if (!sd->device) {
329                 /* The caller might have gotten the udev event before we were
330                  * able to process it. Hence, fake the "add" event and let the
331                  * logind-manager handle the new device. */
332                 r = manager_process_seat_device(sd->session->manager, dev);
333                 if (r < 0)
334                         goto err_dev;
335
336                 /* if it's still not available, then the device is invalid */
337                 sd->device = hashmap_get(sd->session->manager->devices, sp);
338                 if (!sd->device) {
339                         r = -ENODEV;
340                         goto err_dev;
341                 }
342         }
343
344         if (sd->device->seat != sd->session->seat) {
345                 r = -EPERM;
346                 goto err_dev;
347         }
348
349         sd->node = strdup(node);
350         if (!sd->node) {
351                 r = -ENOMEM;
352                 goto err_dev;
353         }
354
355         r = 0;
356 err_dev:
357         udev_device_unref(p ? : dev);
358         return r;
359 }
360
361 int session_device_new(Session *s, dev_t dev, SessionDevice **out) {
362         SessionDevice *sd;
363         int r;
364
365         assert(s);
366         assert(out);
367
368         if (!s->seat)
369                 return -EPERM;
370
371         sd = new0(SessionDevice, 1);
372         if (!sd)
373                 return -ENOMEM;
374
375         sd->session = s;
376         sd->dev = dev;
377         sd->fd = -1;
378         sd->type = DEVICE_TYPE_UNKNOWN;
379
380         r = session_device_verify(sd);
381         if (r < 0)
382                 goto error;
383
384         r = hashmap_put(s->devices, &sd->dev, sd);
385         if (r < 0) {
386                 r = -ENOMEM;
387                 goto error;
388         }
389
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         LIST_PREPEND(SessionDevice, sd_by_device, sd->device->session_devices, sd);
408
409         *out = sd;
410         return 0;
411
412 error:
413         hashmap_remove(s->devices, &sd->dev);
414         free(sd->node);
415         free(sd);
416         return r;
417 }
418
419 void session_device_free(SessionDevice *sd) {
420         assert(sd);
421
422         session_device_stop(sd);
423         session_device_notify(sd, SESSION_DEVICE_RELEASE);
424         close_nointr_nofail(sd->fd);
425
426         LIST_REMOVE(SessionDevice, sd_by_device, sd->device->session_devices, sd);
427
428         hashmap_remove(sd->session->devices, &sd->dev);
429
430         free(sd->node);
431         free(sd);
432 }
433
434 void session_device_complete_pause(SessionDevice *sd) {
435         SessionDevice *iter;
436         Iterator i;
437
438         if (!sd->active)
439                 return;
440
441         session_device_stop(sd);
442
443         /* if not all devices are paused, wait for further completion events */
444         HASHMAP_FOREACH(iter, sd->session->devices, i)
445                 if (iter->active)
446                         return;
447
448         /* complete any pending session switch */
449         seat_complete_switch(sd->session->seat);
450 }
451
452 void session_device_resume_all(Session *s) {
453         SessionDevice *sd;
454         Iterator i;
455         int r;
456
457         assert(s);
458
459         HASHMAP_FOREACH(sd, s->devices, i) {
460                 if (!sd->active) {
461                         r = session_device_start(sd);
462                         if (!r)
463                                 session_device_notify(sd, SESSION_DEVICE_RESUME);
464                 }
465         }
466 }
467
468 void session_device_pause_all(Session *s) {
469         SessionDevice *sd;
470         Iterator i;
471
472         assert(s);
473
474         HASHMAP_FOREACH(sd, s->devices, i) {
475                 if (sd->active) {
476                         session_device_stop(sd);
477                         session_device_notify(sd, SESSION_DEVICE_PAUSE);
478                 }
479         }
480 }
481
482 unsigned int session_device_try_pause_all(Session *s) {
483         SessionDevice *sd;
484         Iterator i;
485         unsigned int num_pending = 0;
486
487         assert(s);
488
489         HASHMAP_FOREACH(sd, s->devices, i) {
490                 if (sd->active) {
491                         session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE);
492                         ++num_pending;
493                 }
494         }
495
496         return num_pending;
497 }