]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-session-device.c
Merge pull request #2589 from keszybz/resolve-tool-2
[thirdparty/systemd.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 }