chiark / gitweb /
logind: fix session-device dbus notify
[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;
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                         sd_drmsetmaster(fd);
160                 else {
161                         /* DRM-Master is granted to the first user who opens a
162                          * device automatically (ughh, racy!). Hence, we just
163                          * drop DRM-Master in case we were the first. */
164                         sd_drmdropmaster(fd);
165                 }
166                 break;
167         case DEVICE_TYPE_EVDEV:
168                 if (!active)
169                         sd_eviocrevoke(fd);
170                 break;
171         case DEVICE_TYPE_FBDEV:
172         case DEVICE_TYPE_UNKNOWN:
173         default:
174                 /* fallback for devices wihout synchronizations */
175                 break;
176         }
177
178         return fd;
179 }
180
181 static int session_device_start(SessionDevice *sd) {
182         int r;
183
184         assert(sd);
185         assert(session_is_active(sd->session));
186
187         if (sd->active)
188                 return 0;
189
190         switch (sd->type) {
191         case DEVICE_TYPE_DRM:
192                 /* Device is kept open. Simply call drmSetMaster() and hope
193                  * there is no-one else. In case it fails, we keep the device
194                  * paused. Maybe at some point we have a drmStealMaster(). */
195                 r = sd_drmsetmaster(sd->fd);
196                 if (r < 0)
197                         return r;
198                 break;
199         case DEVICE_TYPE_EVDEV:
200                 /* Evdev devices are revoked while inactive. Reopen it and we
201                  * are fine. */
202                 r = session_device_open(sd, true);
203                 if (r < 0)
204                         return r;
205                 close_nointr_nofail(sd->fd);
206                 sd->fd = r;
207                 break;
208         case DEVICE_TYPE_FBDEV:
209                 /* fbdev devices have no way to synchronize access. Moreover,
210                  * they mostly operate through mmaps() without any pageflips
211                  * and modesetting, so there is no way for us to prevent access
212                  * but tear down mmaps.
213                  * That would be quite expensive to do on a per-fd context. So
214                  * ignore legcy fbdev and let its users feel the pain they asked
215                  * for when deciding for fbdev. */
216         case DEVICE_TYPE_UNKNOWN:
217         default:
218                 /* fallback for devices wihout synchronizations */
219                 break;
220         }
221
222         sd->active = true;
223         return 0;
224 }
225
226 static void session_device_stop(SessionDevice *sd) {
227         assert(sd);
228
229         if (!sd->active)
230                 return;
231
232         switch (sd->type) {
233         case DEVICE_TYPE_DRM:
234                 /* On DRM devices we simply drop DRM-Master but keep it open.
235                  * This allows the user to keep resources allocated. The
236                  * CAP_SYS_ADMIN restriction to DRM-Master prevents users from
237                  * circumventing this. */
238                 sd_drmdropmaster(sd->fd);
239                 break;
240         case DEVICE_TYPE_EVDEV:
241                 /* Revoke access on evdev file-descriptors during deactivation.
242                  * This will basically prevent any operations on the fd and
243                  * cannot be undone. Good side is: it needs no CAP_SYS_ADMIN
244                  * protection this way. */
245                 sd_eviocrevoke(sd->fd);
246                 break;
247         case DEVICE_TYPE_FBDEV:
248         case DEVICE_TYPE_UNKNOWN:
249         default:
250                 /* fallback for devices without synchronization */
251                 break;
252         }
253
254         sd->active = false;
255 }
256
257 static DeviceType detect_device_type(struct udev_device *dev) {
258         const char *sysname, *subsystem;
259         DeviceType type;
260
261         sysname = udev_device_get_sysname(dev);
262         subsystem = udev_device_get_subsystem(dev);
263         type = DEVICE_TYPE_UNKNOWN;
264
265         if (streq_ptr(subsystem, "graphics")) {
266                 if (!streq(sysname, "fbcon") && startswith(sysname, "fb"))
267                         type = DEVICE_TYPE_FBDEV;
268         } else if (streq_ptr(subsystem, "drm")) {
269                 if (startswith(sysname, "card"))
270                         type = DEVICE_TYPE_DRM;
271         } else if (streq_ptr(subsystem, "input")) {
272                 if (startswith(sysname, "event"))
273                         type = DEVICE_TYPE_EVDEV;
274         }
275
276         return type;
277 }
278
279 static int session_device_verify(SessionDevice *sd) {
280         struct udev_device *dev, *p = NULL;
281         const char *sp, *node;
282         int r;
283
284         dev = udev_device_new_from_devnum(sd->session->manager->udev, 'c', sd->dev);
285         if (!dev)
286                 return -ENODEV;
287
288         sp = udev_device_get_syspath(dev);
289         node = udev_device_get_devnode(dev);
290         if (!node) {
291                 r = -EINVAL;
292                 goto err_dev;
293         }
294
295         /* detect device type so we can find the correct sysfs parent */
296         sd->type = detect_device_type(dev);
297         if (sd->type == DEVICE_TYPE_UNKNOWN) {
298                 r = -ENODEV;
299                 goto err_dev;
300         } else if (sd->type == DEVICE_TYPE_EVDEV) {
301                 /* for evdev devices we need the parent node as device */
302                 p = dev;
303                 dev = udev_device_get_parent_with_subsystem_devtype(p, "input", NULL);
304                 if (!dev) {
305                         r = -ENODEV;
306                         goto err_dev;
307                 }
308                 sp = udev_device_get_syspath(dev);
309         } else if (sd->type != DEVICE_TYPE_FBDEV &&
310                    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, 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         /* Open the device for the first time. We need a valid fd to pass back
383          * to the caller. If the session is not active, this _might_ immediately
384          * revoke access and thus invalidate the fd. But this is still needed
385          * to pass a valid fd back. */
386         sd->active = session_is_active(s);
387         sd->fd = session_device_open(sd, sd->active);
388         if (sd->fd < 0)
389                 goto error;
390
391         LIST_PREPEND(SessionDevice, sd_by_device, sd->device->session_devices, sd);
392
393         *out = sd;
394         return 0;
395
396 error:
397         hashmap_remove(s->devices, &sd->dev);
398         free(sd->node);
399         free(sd);
400         return r;
401 }
402
403 void session_device_free(SessionDevice *sd) {
404         assert(sd);
405
406         session_device_stop(sd);
407         session_device_notify(sd, SESSION_DEVICE_RELEASE);
408         close_nointr_nofail(sd->fd);
409
410         LIST_REMOVE(SessionDevice, sd_by_device, sd->device->session_devices, sd);
411
412         hashmap_remove(sd->session->devices, &sd->dev);
413
414         free(sd->node);
415         free(sd);
416 }
417
418 void session_device_complete_pause(SessionDevice *sd) {
419         SessionDevice *iter;
420         Iterator i;
421
422         if (!sd->active)
423                 return;
424
425         session_device_stop(sd);
426
427         /* if not all devices are paused, wait for further completion events */
428         HASHMAP_FOREACH(iter, sd->session->devices, i)
429                 if (iter->active)
430                         return;
431
432         /* complete any pending session switch */
433         seat_complete_switch(sd->session->seat);
434 }
435
436 void session_device_resume_all(Session *s) {
437         SessionDevice *sd;
438         Iterator i;
439         int r;
440
441         assert(s);
442
443         HASHMAP_FOREACH(sd, s->devices, i) {
444                 if (!sd->active) {
445                         r = session_device_start(sd);
446                         if (!r)
447                                 session_device_notify(sd, SESSION_DEVICE_RESUME);
448                 }
449         }
450 }
451
452 void session_device_pause_all(Session *s) {
453         SessionDevice *sd;
454         Iterator i;
455
456         assert(s);
457
458         HASHMAP_FOREACH(sd, s->devices, i) {
459                 if (sd->active) {
460                         session_device_stop(sd);
461                         session_device_notify(sd, SESSION_DEVICE_PAUSE);
462                 }
463         }
464 }
465
466 unsigned int session_device_try_pause_all(Session *s) {
467         SessionDevice *sd;
468         Iterator i;
469         unsigned int num_pending = 0;
470
471         assert(s);
472
473         HASHMAP_FOREACH(sd, s->devices, i) {
474                 if (sd->active) {
475                         session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE);
476                         ++num_pending;
477                 }
478         }
479
480         return num_pending;
481 }