chiark / gitweb /
logind: trivial simplification
[elogind.git] / src / login / logind-session-device.c
1 /***
2   This file is part of systemd.
3
4   Copyright 2013 David Herrmann
5
6   systemd is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as published by
8   the Free Software Foundation; either version 2.1 of the License, or
9   (at your option) any later version.
10
11   systemd is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   Lesser General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public License
17   along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include <fcntl.h>
21 #include <linux/input.h>
22 #include <string.h>
23 #include <sys/ioctl.h>
24 #include <sys/types.h>
25
26 #if 0 /// elogind needs the systems udev header
27 #include "libudev.h"
28 #else
29 #include <libudev.h>
30 #endif // 0
31
32 #include "alloc-util.h"
33 #include "bus-util.h"
34 #include "fd-util.h"
35 #include "logind-session-device.h"
36 #include "missing.h"
37 #include "util.h"
38
39 enum SessionDeviceNotifications {
40         SESSION_DEVICE_RESUME,
41         SESSION_DEVICE_TRY_PAUSE,
42         SESSION_DEVICE_PAUSE,
43         SESSION_DEVICE_RELEASE,
44 };
45
46 static int session_device_notify(SessionDevice *sd, enum SessionDeviceNotifications type) {
47         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
48         _cleanup_free_ char *path = NULL;
49         const char *t = NULL;
50         uint32_t major, minor;
51         int r;
52
53         assert(sd);
54
55         major = major(sd->dev);
56         minor = minor(sd->dev);
57
58         if (!sd->session->controller)
59                 return 0;
60
61         path = session_bus_path(sd->session);
62         if (!path)
63                 return -ENOMEM;
64
65         r = sd_bus_message_new_signal(
66                         sd->session->manager->bus,
67                         &m, path,
68                         "org.freedesktop.login1.Session",
69                         (type == SESSION_DEVICE_RESUME) ? "ResumeDevice" : "PauseDevice");
70         if (!m)
71                 return r;
72
73         r = sd_bus_message_set_destination(m, sd->session->controller);
74         if (r < 0)
75                 return r;
76
77         switch (type) {
78         case SESSION_DEVICE_RESUME:
79                 r = sd_bus_message_append(m, "uuh", major, minor, sd->fd);
80                 if (r < 0)
81                         return r;
82                 break;
83         case SESSION_DEVICE_TRY_PAUSE:
84                 t = "pause";
85                 break;
86         case SESSION_DEVICE_PAUSE:
87                 t = "force";
88                 break;
89         case SESSION_DEVICE_RELEASE:
90                 t = "gone";
91                 break;
92         default:
93                 return -EINVAL;
94         }
95
96         if (t) {
97                 r = sd_bus_message_append(m, "uus", major, minor, t);
98                 if (r < 0)
99                         return r;
100         }
101
102         return sd_bus_send(sd->session->manager->bus, m, NULL);
103 }
104
105 static int sd_eviocrevoke(int fd) {
106         static bool warned;
107         int r;
108
109         assert(fd >= 0);
110
111         r = ioctl(fd, EVIOCREVOKE, NULL);
112         if (r < 0) {
113                 r = -errno;
114                 if (r == -EINVAL && !warned) {
115                         warned = true;
116                         log_warning("kernel does not support evdev-revocation");
117                 }
118         }
119
120         return 0;
121 }
122
123 static int sd_drmsetmaster(int fd) {
124         int r;
125
126         assert(fd >= 0);
127
128         r = ioctl(fd, DRM_IOCTL_SET_MASTER, 0);
129         if (r < 0)
130                 return -errno;
131
132         return 0;
133 }
134
135 static int sd_drmdropmaster(int fd) {
136         int r;
137
138         assert(fd >= 0);
139
140         r = ioctl(fd, DRM_IOCTL_DROP_MASTER, 0);
141         if (r < 0)
142                 return -errno;
143
144         return 0;
145 }
146
147 static int session_device_open(SessionDevice *sd, bool active) {
148         int fd, r;
149
150         assert(sd->type != DEVICE_TYPE_UNKNOWN);
151
152         /* open device and try to get an udev_device from it */
153         fd = open(sd->node, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
154         if (fd < 0)
155                 return -errno;
156
157         switch (sd->type) {
158         case DEVICE_TYPE_DRM:
159                 if (active) {
160                         /* Weird legacy DRM semantics might return an error
161                          * even though we're master. No way to detect that so
162                          * fail at all times and let caller retry in inactive
163                          * state. */
164                         r = sd_drmsetmaster(fd);
165                         if (r < 0) {
166                                 close_nointr(fd);
167                                 return r;
168                         }
169                 } else {
170                         /* DRM-Master is granted to the first user who opens a
171                          * device automatically (ughh, racy!). Hence, we just
172                          * drop DRM-Master in case we were the first. */
173                         sd_drmdropmaster(fd);
174                 }
175                 break;
176         case DEVICE_TYPE_EVDEV:
177                 if (!active)
178                         sd_eviocrevoke(fd);
179                 break;
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(sd->fd);
214                 sd->fd = r;
215                 break;
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_UNKNOWN:
248         default:
249                 /* fallback for devices without synchronization */
250                 break;
251         }
252
253         sd->active = false;
254 }
255
256 static DeviceType detect_device_type(struct udev_device *dev) {
257         const char *sysname, *subsystem;
258         DeviceType type;
259
260         sysname = udev_device_get_sysname(dev);
261         subsystem = udev_device_get_subsystem(dev);
262         type = DEVICE_TYPE_UNKNOWN;
263
264         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_DRM) {
306                 /* Prevent opening unsupported devices. Especially devices of
307                  * subsystem "input" must be opened via the evdev node as
308                  * we require EVIOCREVOKE. */
309                 r = -ENODEV;
310                 goto err_dev;
311         }
312
313         /* search for an existing seat device and return it if available */
314         sd->device = hashmap_get(sd->session->manager->devices, sp);
315         if (!sd->device) {
316                 /* The caller might have gotten the udev event before we were
317                  * able to process it. Hence, fake the "add" event and let the
318                  * logind-manager handle the new device. */
319                 r = manager_process_seat_device(sd->session->manager, dev);
320                 if (r < 0)
321                         goto err_dev;
322
323                 /* if it's still not available, then the device is invalid */
324                 sd->device = hashmap_get(sd->session->manager->devices, sp);
325                 if (!sd->device) {
326                         r = -ENODEV;
327                         goto err_dev;
328                 }
329         }
330
331         if (sd->device->seat != sd->session->seat) {
332                 r = -EPERM;
333                 goto err_dev;
334         }
335
336         sd->node = strdup(node);
337         if (!sd->node) {
338                 r = -ENOMEM;
339                 goto err_dev;
340         }
341
342         r = 0;
343 err_dev:
344         udev_device_unref(p ? : dev);
345         return r;
346 }
347
348 int session_device_new(Session *s, dev_t dev, SessionDevice **out) {
349         SessionDevice *sd;
350         int r;
351
352         assert(s);
353         assert(out);
354
355         if (!s->seat)
356                 return -EPERM;
357
358         sd = new0(SessionDevice, 1);
359         if (!sd)
360                 return -ENOMEM;
361
362         sd->session = s;
363         sd->dev = dev;
364         sd->fd = -1;
365         sd->type = DEVICE_TYPE_UNKNOWN;
366
367         r = session_device_verify(sd);
368         if (r < 0)
369                 goto error;
370
371         r = hashmap_put(s->devices, &sd->dev, sd);
372         if (r < 0) {
373                 r = -ENOMEM;
374                 goto error;
375         }
376
377         /* Open the device for the first time. We need a valid fd to pass back
378          * to the caller. If the session is not active, this _might_ immediately
379          * revoke access and thus invalidate the fd. But this is still needed
380          * to pass a valid fd back. */
381         sd->active = session_is_active(s);
382         r = session_device_open(sd, sd->active);
383         if (r < 0) {
384                 /* EINVAL _may_ mean a master is active; retry inactive */
385                 if (sd->active && r == -EINVAL) {
386                         sd->active = false;
387                         r = session_device_open(sd, false);
388                 }
389                 if (r < 0)
390                         goto error;
391         }
392         sd->fd = r;
393
394         LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
395
396         *out = sd;
397         return 0;
398
399 error:
400         hashmap_remove(s->devices, &sd->dev);
401         free(sd->node);
402         free(sd);
403         return r;
404 }
405
406 void session_device_free(SessionDevice *sd) {
407         assert(sd);
408
409         session_device_stop(sd);
410         session_device_notify(sd, SESSION_DEVICE_RELEASE);
411         close_nointr(sd->fd);
412
413         LIST_REMOVE(sd_by_device, sd->device->session_devices, sd);
414
415         hashmap_remove(sd->session->devices, &sd->dev);
416
417         free(sd->node);
418         free(sd);
419 }
420
421 void session_device_complete_pause(SessionDevice *sd) {
422         SessionDevice *iter;
423         Iterator i;
424
425         if (!sd->active)
426                 return;
427
428         session_device_stop(sd);
429
430         /* if not all devices are paused, wait for further completion events */
431         HASHMAP_FOREACH(iter, sd->session->devices, i)
432                 if (iter->active)
433                         return;
434
435         /* complete any pending session switch */
436         seat_complete_switch(sd->session->seat);
437 }
438
439 void session_device_resume_all(Session *s) {
440         SessionDevice *sd;
441         Iterator i;
442         int r;
443
444         assert(s);
445
446         HASHMAP_FOREACH(sd, s->devices, i) {
447                 if (!sd->active) {
448                         r = session_device_start(sd);
449                         if (!r)
450                                 session_device_notify(sd, SESSION_DEVICE_RESUME);
451                 }
452         }
453 }
454
455 void session_device_pause_all(Session *s) {
456         SessionDevice *sd;
457         Iterator i;
458
459         assert(s);
460
461         HASHMAP_FOREACH(sd, s->devices, i) {
462                 if (sd->active) {
463                         session_device_stop(sd);
464                         session_device_notify(sd, SESSION_DEVICE_PAUSE);
465                 }
466         }
467 }
468
469 unsigned int session_device_try_pause_all(Session *s) {
470         SessionDevice *sd;
471         Iterator i;
472         unsigned int num_pending = 0;
473
474         assert(s);
475
476         HASHMAP_FOREACH(sd, s->devices, i) {
477                 if (sd->active) {
478                         session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE);
479                         ++num_pending;
480                 }
481         }
482
483         return num_pending;
484 }