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