]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-session-device.c
Merge pull request #57 from pwithnall/wip/pwithnall/udev-virtualbox-rules
[thirdparty/systemd.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 }