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