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