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