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