chiark / gitweb /
b45f9ebe0cc50ba2fa5bbf9cd641dadad8697d20
[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
50         assert(sd);
51
52         if (!sd->session->controller)
53                 return;
54
55         path = session_bus_path(sd->session);
56         if (!path)
57                 return;
58
59         m = dbus_message_new_signal(path,
60                                     "org.freedesktop.login1.Session",
61                                     (type == SESSION_DEVICE_RESUME) ? "ResumeDevice" : "PauseDevice");
62         if (!m)
63                 return;
64
65         if (!dbus_message_set_destination(m, sd->session->controller))
66                 return;
67
68         switch (type) {
69         case SESSION_DEVICE_RESUME:
70                 if (!dbus_message_append_args(m,
71                                               DBUS_TYPE_UINT32, major(sd->dev),
72                                               DBUS_TYPE_UINT32, minor(sd->dev),
73                                               DBUS_TYPE_UNIX_FD, &sd->fd,
74                                               DBUS_TYPE_INVALID))
75                         return;
76                 break;
77         case SESSION_DEVICE_TRY_PAUSE:
78                 t = "pause";
79                 break;
80         case SESSION_DEVICE_PAUSE:
81                 t = "force";
82                 break;
83         case SESSION_DEVICE_RELEASE:
84                 t = "gone";
85                 break;
86         default:
87                 return;
88         }
89
90         if (t && !dbus_message_append_args(m,
91                                            DBUS_TYPE_UINT32, major(sd->dev),
92                                            DBUS_TYPE_UINT32, minor(sd->dev),
93                                            DBUS_TYPE_STRING, &t,
94                                            DBUS_TYPE_INVALID))
95                 return;
96
97         dbus_connection_send(sd->session->manager->bus, m, NULL);
98 }
99
100 static int sd_eviocrevoke(int fd) {
101         static bool warned;
102         int r;
103
104         assert(fd >= 0);
105
106         r = ioctl(fd, EVIOCREVOKE, 1);
107         if (r < 0) {
108                 r = -errno;
109                 if (r == -EINVAL && !warned) {
110                         warned = true;
111                         log_warning("kernel does not support evdev-revocation");
112                 }
113         }
114
115         return 0;
116 }
117
118 static int sd_drmsetmaster(int fd) {
119         int r;
120
121         assert(fd >= 0);
122
123         r = ioctl(fd, DRM_IOCTL_SET_MASTER, 0);
124         if (r < 0)
125                 return -errno;
126
127         return 0;
128 }
129
130 static int sd_drmdropmaster(int fd) {
131         int r;
132
133         assert(fd >= 0);
134
135         r = ioctl(fd, DRM_IOCTL_DROP_MASTER, 0);
136         if (r < 0)
137                 return -errno;
138
139         return 0;
140 }
141
142 static int session_device_open(SessionDevice *sd, bool active) {
143         int fd;
144
145         assert(sd->type != DEVICE_TYPE_UNKNOWN);
146
147         /* open device and try to get an udev_device from it */
148         fd = open(sd->node, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
149         if (fd < 0)
150                 return -errno;
151
152         switch (sd->type) {
153         case DEVICE_TYPE_DRM:
154                 if (active)
155                         sd_drmsetmaster(fd);
156                 else {
157                         /* DRM-Master is granted to the first user who opens a
158                          * device automatically (ughh, racy!). Hence, we just
159                          * drop DRM-Master in case we were the first. */
160                         sd_drmdropmaster(fd);
161                 }
162                 break;
163         case DEVICE_TYPE_EVDEV:
164                 if (!active)
165                         sd_eviocrevoke(fd);
166                 break;
167         case DEVICE_TYPE_FBDEV:
168         case DEVICE_TYPE_UNKNOWN:
169         default:
170                 /* fallback for devices wihout synchronizations */
171                 break;
172         }
173
174         return fd;
175 }
176
177 static int session_device_start(SessionDevice *sd) {
178         int r;
179
180         assert(sd);
181         assert(session_is_active(sd->session));
182
183         if (sd->active)
184                 return 0;
185
186         switch (sd->type) {
187         case DEVICE_TYPE_DRM:
188                 /* Device is kept open. Simply call drmSetMaster() and hope
189                  * there is no-one else. In case it fails, we keep the device
190                  * paused. Maybe at some point we have a drmStealMaster(). */
191                 r = sd_drmsetmaster(sd->fd);
192                 if (r < 0)
193                         return r;
194                 break;
195         case DEVICE_TYPE_EVDEV:
196                 /* Evdev devices are revoked while inactive. Reopen it and we
197                  * are fine. */
198                 r = session_device_open(sd, true);
199                 if (r < 0)
200                         return r;
201                 close_nointr_nofail(sd->fd);
202                 sd->fd = r;
203                 break;
204         case DEVICE_TYPE_FBDEV:
205                 /* fbdev devices have no way to synchronize access. Moreover,
206                  * they mostly operate through mmaps() without any pageflips
207                  * and modesetting, so there is no way for us to prevent access
208                  * but tear down mmaps.
209                  * That would be quite expensive to do on a per-fd context. So
210                  * ignore legcy fbdev and let its users feel the pain they asked
211                  * for when deciding for fbdev. */
212         case DEVICE_TYPE_UNKNOWN:
213         default:
214                 /* fallback for devices wihout synchronizations */
215                 break;
216         }
217
218         sd->active = true;
219         return 0;
220 }
221
222 static void session_device_stop(SessionDevice *sd) {
223         assert(sd);
224
225         if (!sd->active)
226                 return;
227
228         switch (sd->type) {
229         case DEVICE_TYPE_DRM:
230                 /* On DRM devices we simply drop DRM-Master but keep it open.
231                  * This allows the user to keep resources allocated. The
232                  * CAP_SYS_ADMIN restriction to DRM-Master prevents users from
233                  * circumventing this. */
234                 sd_drmdropmaster(sd->fd);
235                 break;
236         case DEVICE_TYPE_EVDEV:
237                 /* Revoke access on evdev file-descriptors during deactivation.
238                  * This will basically prevent any operations on the fd and
239                  * cannot be undone. Good side is: it needs no CAP_SYS_ADMIN
240                  * protection this way. */
241                 sd_eviocrevoke(sd->fd);
242                 break;
243         case DEVICE_TYPE_FBDEV:
244         case DEVICE_TYPE_UNKNOWN:
245         default:
246                 /* fallback for devices without synchronization */
247                 break;
248         }
249
250         sd->active = false;
251 }
252
253 static DeviceType detect_device_type(struct udev_device *dev) {
254         const char *sysname, *subsystem;
255         DeviceType type;
256
257         sysname = udev_device_get_sysname(dev);
258         subsystem = udev_device_get_subsystem(dev);
259         type = DEVICE_TYPE_UNKNOWN;
260
261         if (streq_ptr(subsystem, "graphics")) {
262                 if (!streq(sysname, "fbcon") && startswith(sysname, "fb"))
263                         type = DEVICE_TYPE_FBDEV;
264         } else if (streq_ptr(subsystem, "drm")) {
265                 if (startswith(sysname, "card"))
266                         type = DEVICE_TYPE_DRM;
267         } else if (streq_ptr(subsystem, "input")) {
268                 if (startswith(sysname, "event"))
269                         type = DEVICE_TYPE_EVDEV;
270         }
271
272         return type;
273 }
274
275 static int session_device_verify(SessionDevice *sd) {
276         struct udev_device *dev, *p = NULL;
277         const char *sp, *node;
278         int r;
279
280         dev = udev_device_new_from_devnum(sd->session->manager->udev, 'c', sd->dev);
281         if (!dev)
282                 return -ENODEV;
283
284         sp = udev_device_get_syspath(dev);
285         node = udev_device_get_devnode(dev);
286         if (!node) {
287                 r = -EINVAL;
288                 goto err_dev;
289         }
290
291         /* detect device type so we can find the correct sysfs parent */
292         sd->type = detect_device_type(dev);
293         if (sd->type == DEVICE_TYPE_UNKNOWN) {
294                 r = -ENODEV;
295                 goto err_dev;
296         } else if (sd->type == DEVICE_TYPE_EVDEV) {
297                 /* for evdev devices we need the parent node as device */
298                 p = dev;
299                 dev = udev_device_get_parent_with_subsystem_devtype(p, "input", NULL);
300                 if (!dev) {
301                         r = -ENODEV;
302                         goto err_dev;
303                 }
304                 sp = udev_device_get_syspath(dev);
305         } else if (sd->type != DEVICE_TYPE_FBDEV &&
306                    sd->type != DEVICE_TYPE_DRM) {
307                 /* Prevent opening unsupported devices. Especially devices of
308                  * subsystem "input" must be opened via the evdev node as
309                  * we require EVIOCREVOKE. */
310                 r = -ENODEV;
311                 goto err_dev;
312         }
313
314         /* search for an existing seat device and return it if available */
315         sd->device = hashmap_get(sd->session->manager->devices, sp);
316         if (!sd->device) {
317                 /* The caller might have gotten the udev event before we were
318                  * able to process it. Hence, fake the "add" event and let the
319                  * logind-manager handle the new device. */
320                 r = manager_process_seat_device(sd->session->manager, dev);
321                 if (r < 0)
322                         goto err_dev;
323
324                 /* if it's still not available, then the device is invalid */
325                 sd->device = hashmap_get(sd->session->manager->devices, sp);
326                 if (!sd->device) {
327                         r = -ENODEV;
328                         goto err_dev;
329                 }
330         }
331
332         if (sd->device->seat != sd->session->seat) {
333                 r = -EPERM;
334                 goto err_dev;
335         }
336
337         sd->node = strdup(node);
338         if (!sd->node) {
339                 r = -ENOMEM;
340                 goto err_dev;
341         }
342
343         r = 0;
344 err_dev:
345         udev_device_unref(p ? : dev);
346         return r;
347 }
348
349 int session_device_new(Session *s, dev_t dev, SessionDevice **out) {
350         SessionDevice *sd;
351         int r;
352
353         assert(s);
354         assert(out);
355
356         if (!s->seat)
357                 return -EPERM;
358
359         sd = new0(SessionDevice, 1);
360         if (!sd)
361                 return -ENOMEM;
362
363         sd->session = s;
364         sd->dev = dev;
365         sd->fd = -1;
366         sd->type = DEVICE_TYPE_UNKNOWN;
367
368         r = session_device_verify(sd);
369         if (r < 0)
370                 goto error;
371
372         r = hashmap_put(s->devices, &sd->dev, sd);
373         if (r < 0) {
374                 r = -ENOMEM;
375                 goto error;
376         }
377
378         /* Open the device for the first time. We need a valid fd to pass back
379          * to the caller. If the session is not active, this _might_ immediately
380          * revoke access and thus invalidate the fd. But this is still needed
381          * to pass a valid fd back. */
382         sd->active = session_is_active(s);
383         sd->fd = session_device_open(sd, sd->active);
384         if (sd->fd < 0)
385                 goto error;
386
387         LIST_PREPEND(SessionDevice, sd_by_device, sd->device->session_devices, sd);
388
389         *out = sd;
390         return 0;
391
392 error:
393         hashmap_remove(s->devices, &sd->dev);
394         free(sd->node);
395         free(sd);
396         return r;
397 }
398
399 void session_device_free(SessionDevice *sd) {
400         assert(sd);
401
402         session_device_stop(sd);
403         session_device_notify(sd, SESSION_DEVICE_RELEASE);
404         close_nointr_nofail(sd->fd);
405
406         LIST_REMOVE(SessionDevice, sd_by_device, sd->device->session_devices, sd);
407
408         hashmap_remove(sd->session->devices, &sd->dev);
409
410         free(sd->node);
411         free(sd);
412 }
413
414 void session_device_complete_pause(SessionDevice *sd) {
415         SessionDevice *iter;
416         Iterator i;
417
418         if (!sd->active)
419                 return;
420
421         session_device_stop(sd);
422
423         /* if not all devices are paused, wait for further completion events */
424         HASHMAP_FOREACH(iter, sd->session->devices, i)
425                 if (iter->active)
426                         return;
427
428         /* complete any pending session switch */
429         seat_complete_switch(sd->session->seat);
430 }
431
432 void session_device_resume_all(Session *s) {
433         SessionDevice *sd;
434         Iterator i;
435         int r;
436
437         assert(s);
438
439         HASHMAP_FOREACH(sd, s->devices, i) {
440                 if (!sd->active) {
441                         r = session_device_start(sd);
442                         if (!r)
443                                 session_device_notify(sd, SESSION_DEVICE_RESUME);
444                 }
445         }
446 }
447
448 void session_device_pause_all(Session *s) {
449         SessionDevice *sd;
450         Iterator i;
451
452         assert(s);
453
454         HASHMAP_FOREACH(sd, s->devices, i) {
455                 if (sd->active) {
456                         session_device_stop(sd);
457                         session_device_notify(sd, SESSION_DEVICE_PAUSE);
458                 }
459         }
460 }
461
462 unsigned int session_device_try_pause_all(Session *s) {
463         SessionDevice *sd;
464         Iterator i;
465         unsigned int num_pending = 0;
466
467         assert(s);
468
469         HASHMAP_FOREACH(sd, s->devices, i) {
470                 if (sd->active) {
471                         session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE);
472                         ++num_pending;
473                 }
474         }
475
476         return num_pending;
477 }