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