]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/logind-session-device.c
util-lib: split string parsing related calls from util.[ch] into parse-util.[ch]
[thirdparty/systemd.git] / src / login / logind-session-device.c
CommitLineData
118ecf32
DH
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
118ecf32 22#include <fcntl.h>
118ecf32 23#include <linux/input.h>
118ecf32
DH
24#include <string.h>
25#include <sys/ioctl.h>
118ecf32 26#include <sys/types.h>
118ecf32 27
07630cea
LP
28#include "libudev.h"
29
cc377381 30#include "bus-util.h"
3ffd4af2 31#include "fd-util.h"
cc377381 32#include "logind-session-device.h"
3ffd4af2
LP
33#include "missing.h"
34#include "util.h"
118ecf32
DH
35
36enum SessionDeviceNotifications {
37 SESSION_DEVICE_RESUME,
38 SESSION_DEVICE_TRY_PAUSE,
39 SESSION_DEVICE_PAUSE,
40 SESSION_DEVICE_RELEASE,
41};
42
cc377381
LP
43static int session_device_notify(SessionDevice *sd, enum SessionDeviceNotifications type) {
44 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
118ecf32
DH
45 _cleanup_free_ char *path = NULL;
46 const char *t = NULL;
081dfa85 47 uint32_t major, minor;
cc377381 48 int r;
118ecf32
DH
49
50 assert(sd);
51
081dfa85
DH
52 major = major(sd->dev);
53 minor = minor(sd->dev);
54
118ecf32 55 if (!sd->session->controller)
cc377381 56 return 0;
118ecf32
DH
57
58 path = session_bus_path(sd->session);
59 if (!path)
cc377381 60 return -ENOMEM;
118ecf32 61
cc377381 62 r = sd_bus_message_new_signal(
151b9b96
LP
63 sd->session->manager->bus,
64 &m, path,
cc377381 65 "org.freedesktop.login1.Session",
151b9b96 66 (type == SESSION_DEVICE_RESUME) ? "ResumeDevice" : "PauseDevice");
118ecf32 67 if (!m)
cc377381 68 return r;
118ecf32 69
cc377381
LP
70 r = sd_bus_message_set_destination(m, sd->session->controller);
71 if (r < 0)
72 return r;
118ecf32
DH
73
74 switch (type) {
75 case SESSION_DEVICE_RESUME:
cc377381
LP
76 r = sd_bus_message_append(m, "uuh", major, minor, sd->fd);
77 if (r < 0)
78 return r;
118ecf32
DH
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:
cc377381 90 return -EINVAL;
118ecf32
DH
91 }
92
cc377381
LP
93 if (t) {
94 r = sd_bus_message_append(m, "uus", major, minor, t);
95 if (r < 0)
96 return r;
97 }
118ecf32 98
cc377381 99 return sd_bus_send(sd->session->manager->bus, m, NULL);
118ecf32
DH
100}
101
102static int sd_eviocrevoke(int fd) {
103 static bool warned;
104 int r;
105
106 assert(fd >= 0);
107
8dbce34b 108 r = ioctl(fd, EVIOCREVOKE, NULL);
118ecf32
DH
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
120static 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
132static 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
144static int session_device_open(SessionDevice *sd, bool active) {
c2e5d024 145 int fd, r;
118ecf32
DH
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:
c2e5d024
DH
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) {
d1107170 163 close_nointr(fd);
c2e5d024
DH
164 return r;
165 }
166 } else {
118ecf32
DH
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;
118ecf32
DH
177 case DEVICE_TYPE_UNKNOWN:
178 default:
179 /* fallback for devices wihout synchronizations */
180 break;
181 }
182
183 return fd;
184}
185
186static 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;
d1107170 210 close_nointr(sd->fd);
118ecf32
DH
211 sd->fd = r;
212 break;
118ecf32
DH
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
223static 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;
118ecf32
DH
244 case DEVICE_TYPE_UNKNOWN:
245 default:
246 /* fallback for devices without synchronization */
247 break;
248 }
249
250 sd->active = false;
251}
252
253static 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
3a83f522 261 if (streq_ptr(subsystem, "drm")) {
118ecf32
DH
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
272static 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);
3a83f522 302 } else if (sd->type != DEVICE_TYPE_DRM) {
118ecf32
DH
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;
340err_dev:
341 udev_device_unref(p ? : dev);
342 return r;
343}
344
345int 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
831dedef 368 r = hashmap_put(s->devices, &sd->dev, sd);
118ecf32
DH
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);
c2e5d024
DH
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;
118ecf32 390
71fda00f 391 LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
118ecf32
DH
392
393 *out = sd;
394 return 0;
395
396error:
831dedef 397 hashmap_remove(s->devices, &sd->dev);
118ecf32
DH
398 free(sd->node);
399 free(sd);
400 return r;
401}
402
403void session_device_free(SessionDevice *sd) {
404 assert(sd);
405
406 session_device_stop(sd);
407 session_device_notify(sd, SESSION_DEVICE_RELEASE);
d1107170 408 close_nointr(sd->fd);
118ecf32 409
71fda00f 410 LIST_REMOVE(sd_by_device, sd->device->session_devices, sd);
118ecf32 411
831dedef 412 hashmap_remove(sd->session->devices, &sd->dev);
118ecf32
DH
413
414 free(sd->node);
415 free(sd);
416}
417
418void session_device_complete_pause(SessionDevice *sd) {
d7bd01b5
DH
419 SessionDevice *iter;
420 Iterator i;
421
118ecf32
DH
422 if (!sd->active)
423 return;
424
425 session_device_stop(sd);
d7bd01b5
DH
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);
118ecf32
DH
434}
435
436void 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
452void 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}
d7bd01b5
DH
465
466unsigned 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}