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