]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/logind-session-device.c
Merge pull request #2049 from evverx/journal-test-dont-run-on-incomplete-setup
[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
b4bbcaa9
TA
28#include "libudev.h"
29
b5efdb8a 30#include "alloc-util.h"
cc377381 31#include "bus-util.h"
3ffd4af2 32#include "fd-util.h"
cc377381 33#include "logind-session-device.h"
3ffd4af2
LP
34#include "missing.h"
35#include "util.h"
118ecf32
DH
36
37enum SessionDeviceNotifications {
38 SESSION_DEVICE_RESUME,
39 SESSION_DEVICE_TRY_PAUSE,
40 SESSION_DEVICE_PAUSE,
41 SESSION_DEVICE_RELEASE,
42};
43
cc377381
LP
44static int session_device_notify(SessionDevice *sd, enum SessionDeviceNotifications type) {
45 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
118ecf32
DH
46 _cleanup_free_ char *path = NULL;
47 const char *t = NULL;
081dfa85 48 uint32_t major, minor;
cc377381 49 int r;
118ecf32
DH
50
51 assert(sd);
52
081dfa85
DH
53 major = major(sd->dev);
54 minor = minor(sd->dev);
55
118ecf32 56 if (!sd->session->controller)
cc377381 57 return 0;
118ecf32
DH
58
59 path = session_bus_path(sd->session);
60 if (!path)
cc377381 61 return -ENOMEM;
118ecf32 62
cc377381 63 r = sd_bus_message_new_signal(
151b9b96
LP
64 sd->session->manager->bus,
65 &m, path,
cc377381 66 "org.freedesktop.login1.Session",
151b9b96 67 (type == SESSION_DEVICE_RESUME) ? "ResumeDevice" : "PauseDevice");
118ecf32 68 if (!m)
cc377381 69 return r;
118ecf32 70
cc377381
LP
71 r = sd_bus_message_set_destination(m, sd->session->controller);
72 if (r < 0)
73 return r;
118ecf32
DH
74
75 switch (type) {
76 case SESSION_DEVICE_RESUME:
cc377381
LP
77 r = sd_bus_message_append(m, "uuh", major, minor, sd->fd);
78 if (r < 0)
79 return r;
118ecf32
DH
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:
cc377381 91 return -EINVAL;
118ecf32
DH
92 }
93
cc377381
LP
94 if (t) {
95 r = sd_bus_message_append(m, "uus", major, minor, t);
96 if (r < 0)
97 return r;
98 }
118ecf32 99
cc377381 100 return sd_bus_send(sd->session->manager->bus, m, NULL);
118ecf32
DH
101}
102
103static int sd_eviocrevoke(int fd) {
104 static bool warned;
105 int r;
106
107 assert(fd >= 0);
108
8dbce34b 109 r = ioctl(fd, EVIOCREVOKE, NULL);
118ecf32
DH
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
121static 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
133static 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
145static int session_device_open(SessionDevice *sd, bool active) {
c2e5d024 146 int fd, r;
118ecf32
DH
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:
c2e5d024
DH
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) {
d1107170 164 close_nointr(fd);
c2e5d024
DH
165 return r;
166 }
167 } else {
118ecf32
DH
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;
118ecf32
DH
178 case DEVICE_TYPE_UNKNOWN:
179 default:
180 /* fallback for devices wihout synchronizations */
181 break;
182 }
183
184 return fd;
185}
186
187static 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;
d1107170 211 close_nointr(sd->fd);
118ecf32
DH
212 sd->fd = r;
213 break;
118ecf32
DH
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
224static 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;
118ecf32
DH
245 case DEVICE_TYPE_UNKNOWN:
246 default:
247 /* fallback for devices without synchronization */
248 break;
249 }
250
251 sd->active = false;
252}
253
254static 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
3a83f522 262 if (streq_ptr(subsystem, "drm")) {
118ecf32
DH
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
273static 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);
3a83f522 303 } else if (sd->type != DEVICE_TYPE_DRM) {
118ecf32
DH
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;
341err_dev:
342 udev_device_unref(p ? : dev);
343 return r;
344}
345
346int 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
831dedef 369 r = hashmap_put(s->devices, &sd->dev, sd);
118ecf32
DH
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);
c2e5d024
DH
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;
118ecf32 391
71fda00f 392 LIST_PREPEND(sd_by_device, sd->device->session_devices, sd);
118ecf32
DH
393
394 *out = sd;
395 return 0;
396
397error:
831dedef 398 hashmap_remove(s->devices, &sd->dev);
118ecf32
DH
399 free(sd->node);
400 free(sd);
401 return r;
402}
403
404void session_device_free(SessionDevice *sd) {
405 assert(sd);
406
407 session_device_stop(sd);
408 session_device_notify(sd, SESSION_DEVICE_RELEASE);
d1107170 409 close_nointr(sd->fd);
118ecf32 410
71fda00f 411 LIST_REMOVE(sd_by_device, sd->device->session_devices, sd);
118ecf32 412
831dedef 413 hashmap_remove(sd->session->devices, &sd->dev);
118ecf32
DH
414
415 free(sd->node);
416 free(sd);
417}
418
419void session_device_complete_pause(SessionDevice *sd) {
d7bd01b5
DH
420 SessionDevice *iter;
421 Iterator i;
422
118ecf32
DH
423 if (!sd->active)
424 return;
425
426 session_device_stop(sd);
d7bd01b5
DH
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);
118ecf32
DH
435}
436
437void 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
453void 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}
d7bd01b5
DH
466
467unsigned 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}