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