]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-terminal/sysview.c
update TODO
[thirdparty/systemd.git] / src / libsystemd-terminal / sysview.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
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 <inttypes.h>
23 #include <libudev.h>
24 #include <stdbool.h>
25 #include <stdlib.h>
26 #include <systemd/sd-bus.h>
27 #include <systemd/sd-event.h>
28 #include <systemd/sd-login.h>
29 #include "bus-util.h"
30 #include "event-util.h"
31 #include "macro.h"
32 #include "set.h"
33 #include "sysview.h"
34 #include "sysview-internal.h"
35 #include "udev-util.h"
36 #include "util.h"
37
38 static int context_raise_session_control(sysview_context *c, sysview_session *session, int error);
39
40 /*
41 * Devices
42 */
43
44 sysview_device *sysview_find_device(sysview_context *c, const char *name) {
45 assert_return(c, NULL);
46 assert_return(name, NULL);
47
48 return hashmap_get(c->device_map, name);
49 }
50
51 int sysview_device_new(sysview_device **out, sysview_seat *seat, const char *name) {
52 _cleanup_(sysview_device_freep) sysview_device *device = NULL;
53 int r;
54
55 assert_return(seat, -EINVAL);
56 assert_return(name, -EINVAL);
57
58 device = new0(sysview_device, 1);
59 if (!device)
60 return -ENOMEM;
61
62 device->seat = seat;
63 device->type = (unsigned)-1;
64
65 device->name = strdup(name);
66 if (!device->name)
67 return -ENOMEM;
68
69 r = hashmap_put(seat->context->device_map, device->name, device);
70 if (r < 0)
71 return r;
72
73 r = hashmap_put(seat->device_map, device->name, device);
74 if (r < 0)
75 return r;
76
77 if (out)
78 *out = device;
79 device = NULL;
80 return 0;
81 }
82
83 sysview_device *sysview_device_free(sysview_device *device) {
84 if (!device)
85 return NULL;
86
87 if (device->name) {
88 hashmap_remove_value(device->seat->device_map, device->name, device);
89 hashmap_remove_value(device->seat->context->device_map, device->name, device);
90 }
91
92 switch (device->type) {
93 case SYSVIEW_DEVICE_EVDEV:
94 device->evdev.ud = udev_device_unref(device->evdev.ud);
95 break;
96 case SYSVIEW_DEVICE_DRM:
97 device->drm.ud = udev_device_unref(device->drm.ud);
98 break;
99 }
100
101 free(device->name);
102 free(device);
103
104 return NULL;
105 }
106
107 const char *sysview_device_get_name(sysview_device *device) {
108 assert_return(device, NULL);
109
110 return device->name;
111 }
112
113 unsigned int sysview_device_get_type(sysview_device *device) {
114 assert_return(device, (unsigned)-1);
115
116 return device->type;
117 }
118
119 struct udev_device *sysview_device_get_ud(sysview_device *device) {
120 assert_return(device, NULL);
121
122 switch (device->type) {
123 case SYSVIEW_DEVICE_EVDEV:
124 return device->evdev.ud;
125 case SYSVIEW_DEVICE_DRM:
126 return device->drm.ud;
127 default:
128 assert_return(0, NULL);
129 }
130 }
131
132 static int device_new_ud(sysview_device **out, sysview_seat *seat, unsigned int type, struct udev_device *ud) {
133 _cleanup_(sysview_device_freep) sysview_device *device = NULL;
134 int r;
135
136 assert_return(seat, -EINVAL);
137 assert_return(ud, -EINVAL);
138
139 r = sysview_device_new(&device, seat, udev_device_get_syspath(ud));
140 if (r < 0)
141 return r;
142
143 device->type = type;
144
145 switch (type) {
146 case SYSVIEW_DEVICE_EVDEV:
147 device->evdev.ud = udev_device_ref(ud);
148 break;
149 case SYSVIEW_DEVICE_DRM:
150 device->drm.ud = udev_device_ref(ud);
151 break;
152 default:
153 assert_not_reached("sysview: invalid udev-device type");
154 }
155
156 if (out)
157 *out = device;
158 device = NULL;
159 return 0;
160 }
161
162 /*
163 * Sessions
164 */
165
166 sysview_session *sysview_find_session(sysview_context *c, const char *name) {
167 assert_return(c, NULL);
168 assert_return(name, NULL);
169
170 return hashmap_get(c->session_map, name);
171 }
172
173 int sysview_session_new(sysview_session **out, sysview_seat *seat, const char *name) {
174 _cleanup_(sysview_session_freep) sysview_session *session = NULL;
175 int r;
176
177 assert_return(seat, -EINVAL);
178
179 session = new0(sysview_session, 1);
180 if (!session)
181 return -ENOMEM;
182
183 session->seat = seat;
184
185 if (name) {
186 /*
187 * If a name is given, we require it to be a logind session
188 * name. The session will be put in managed mode and we use
189 * logind to request controller access.
190 */
191
192 session->name = strdup(name);
193 if (!session->name)
194 return -ENOMEM;
195
196 r = sd_bus_path_encode("/org/freedesktop/login1/session",
197 session->name, &session->path);
198 if (r < 0)
199 return r;
200
201 session->custom = false;;
202 } else {
203 /*
204 * No session name was given. We assume this is an unmanaged
205 * session controlled by the application. We don't use logind
206 * at all and leave session management to the application. The
207 * name of the session-object is set to a unique random string
208 * that does not clash with the logind namespace.
209 */
210
211 r = asprintf(&session->name, "@custom%" PRIu64,
212 ++seat->context->custom_sid);
213 if (r < 0)
214 return -ENOMEM;
215
216 session->custom = true;
217 }
218
219 r = hashmap_put(seat->context->session_map, session->name, session);
220 if (r < 0)
221 return r;
222
223 r = hashmap_put(seat->session_map, session->name, session);
224 if (r < 0)
225 return r;
226
227 if (out)
228 *out = session;
229 session = NULL;
230 return 0;
231 }
232
233 sysview_session *sysview_session_free(sysview_session *session) {
234 if (!session)
235 return NULL;
236
237 assert(!session->public);
238 assert(!session->wants_control);
239
240 if (session->name) {
241 hashmap_remove_value(session->seat->session_map, session->name, session);
242 hashmap_remove_value(session->seat->context->session_map, session->name, session);
243 }
244
245 free(session->path);
246 free(session->name);
247 free(session);
248
249 return NULL;
250 }
251
252 void sysview_session_set_userdata(sysview_session *session, void *userdata) {
253 assert(session);
254
255 session->userdata = userdata;
256 }
257
258 void *sysview_session_get_userdata(sysview_session *session) {
259 assert_return(session, NULL);
260
261 return session->userdata;
262 }
263
264 const char *sysview_session_get_name(sysview_session *session) {
265 assert_return(session, NULL);
266
267 return session->name;
268 }
269
270 sysview_seat *sysview_session_get_seat(sysview_session *session) {
271 assert_return(session, NULL);
272
273 return session->seat;
274 }
275
276 static int session_take_control_fn(sd_bus *bus,
277 sd_bus_message *reply,
278 void *userdata,
279 sd_bus_error *ret_error) {
280 sysview_session *session = userdata;
281 int r, error;
282
283 session->slot_take_control = sd_bus_slot_unref(session->slot_take_control);
284
285 if (sd_bus_message_is_method_error(reply, NULL)) {
286 const sd_bus_error *e = sd_bus_message_get_error(reply);
287
288 log_debug("sysview: %s: TakeControl failed: %s: %s",
289 session->name, e->name, e->message);
290 error = -sd_bus_error_get_errno(e);
291 } else {
292 session->has_control = true;
293 error = 0;
294 }
295
296 r = context_raise_session_control(session->seat->context, session, error);
297 if (r < 0)
298 log_debug_errno(r, "sysview: callback failed while signalling session control '%d' on session '%s': %m",
299 error, session->name);
300
301 return 0;
302 }
303
304 int sysview_session_take_control(sysview_session *session) {
305 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
306 int r;
307
308 assert_return(session, -EINVAL);
309 assert_return(!session->custom, -EINVAL);
310
311 if (session->wants_control)
312 return 0;
313
314 r = sd_bus_message_new_method_call(session->seat->context->sysbus,
315 &m,
316 "org.freedesktop.login1",
317 session->path,
318 "org.freedesktop.login1.Session",
319 "TakeControl");
320 if (r < 0)
321 return r;
322
323 r = sd_bus_message_append(m, "b", 0);
324 if (r < 0)
325 return r;
326
327 r = sd_bus_call_async(session->seat->context->sysbus,
328 &session->slot_take_control,
329 m,
330 session_take_control_fn,
331 session,
332 0);
333 if (r < 0)
334 return r;
335
336 session->wants_control = true;
337 return 0;
338 }
339
340 void sysview_session_release_control(sysview_session *session) {
341 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
342 int r;
343
344 assert(session);
345 assert(!session->custom);
346
347 if (!session->wants_control)
348 return;
349
350 session->wants_control = false;
351
352 if (!session->has_control && !session->slot_take_control)
353 return;
354
355 session->has_control = false;
356 session->slot_take_control = sd_bus_slot_unref(session->slot_take_control);
357
358 r = sd_bus_message_new_method_call(session->seat->context->sysbus,
359 &m,
360 "org.freedesktop.login1",
361 session->path,
362 "org.freedesktop.login1.Session",
363 "ReleaseControl");
364 if (r >= 0)
365 r = sd_bus_send(session->seat->context->sysbus, m, NULL);
366
367 if (r < 0 && r != -ENOTCONN)
368 log_debug_errno(r, "sysview: %s: cannot send ReleaseControl: %m",
369 session->name);
370 }
371
372 /*
373 * Seats
374 */
375
376 sysview_seat *sysview_find_seat(sysview_context *c, const char *name) {
377 assert_return(c, NULL);
378 assert_return(name, NULL);
379
380 return hashmap_get(c->seat_map, name);
381 }
382
383 int sysview_seat_new(sysview_seat **out, sysview_context *c, const char *name) {
384 _cleanup_(sysview_seat_freep) sysview_seat *seat = NULL;
385 int r;
386
387 assert_return(c, -EINVAL);
388 assert_return(name, -EINVAL);
389
390 seat = new0(sysview_seat, 1);
391 if (!seat)
392 return -ENOMEM;
393
394 seat->context = c;
395
396 seat->name = strdup(name);
397 if (!seat->name)
398 return -ENOMEM;
399
400 r = sd_bus_path_encode("/org/freedesktop/login1/seat", seat->name, &seat->path);
401 if (r < 0)
402 return r;
403
404 seat->session_map = hashmap_new(&string_hash_ops);
405 if (!seat->session_map)
406 return -ENOMEM;
407
408 seat->device_map = hashmap_new(&string_hash_ops);
409 if (!seat->device_map)
410 return -ENOMEM;
411
412 r = hashmap_put(c->seat_map, seat->name, seat);
413 if (r < 0)
414 return r;
415
416 if (out)
417 *out = seat;
418 seat = NULL;
419 return 0;
420 }
421
422 sysview_seat *sysview_seat_free(sysview_seat *seat) {
423 if (!seat)
424 return NULL;
425
426 assert(!seat->public);
427 assert(hashmap_size(seat->device_map) == 0);
428 assert(hashmap_size(seat->session_map) == 0);
429
430 if (seat->name)
431 hashmap_remove_value(seat->context->seat_map, seat->name, seat);
432
433 hashmap_free(seat->device_map);
434 hashmap_free(seat->session_map);
435 free(seat->path);
436 free(seat->name);
437 free(seat);
438
439 return NULL;
440 }
441
442 const char *sysview_seat_get_name(sysview_seat *seat) {
443 assert_return(seat, NULL);
444
445 return seat->name;
446 }
447
448 int sysview_seat_switch_to(sysview_seat *seat, uint32_t nr) {
449 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
450 int r;
451
452 assert_return(seat, -EINVAL);
453 assert_return(seat->context->sysbus, -EINVAL);
454
455 r = sd_bus_message_new_method_call(seat->context->sysbus,
456 &m,
457 "org.freedesktop.login1",
458 seat->path,
459 "org.freedesktop.login1.Seat",
460 "SwitchTo");
461 if (r < 0)
462 return r;
463
464 r = sd_bus_message_append(m, "u", nr);
465 if (r < 0)
466 return r;
467
468 return sd_bus_send(seat->context->sysbus, m, NULL);
469 }
470
471 /*
472 * Contexts
473 */
474
475 static int context_raise(sysview_context *c, sysview_event *event, int def) {
476 return c->running ? c->event_fn(c, c->userdata, event) : def;
477 }
478
479 static int context_raise_seat_add(sysview_context *c, sysview_seat *seat) {
480 sysview_event event = {
481 .type = SYSVIEW_EVENT_SEAT_ADD,
482 .seat_add = {
483 .seat = seat,
484 }
485 };
486
487 return context_raise(c, &event, 0);
488 }
489
490 static int context_raise_seat_remove(sysview_context *c, sysview_seat *seat) {
491 sysview_event event = {
492 .type = SYSVIEW_EVENT_SEAT_REMOVE,
493 .seat_remove = {
494 .seat = seat,
495 }
496 };
497
498 return context_raise(c, &event, 0);
499 }
500
501 static int context_raise_session_filter(sysview_context *c,
502 const char *id,
503 const char *seatid,
504 const char *username,
505 unsigned int uid) {
506 sysview_event event = {
507 .type = SYSVIEW_EVENT_SESSION_FILTER,
508 .session_filter = {
509 .id = id,
510 .seatid = seatid,
511 .username = username,
512 .uid = uid,
513 }
514 };
515
516 return context_raise(c, &event, 1);
517 }
518
519 static int context_raise_session_add(sysview_context *c, sysview_session *session) {
520 sysview_event event = {
521 .type = SYSVIEW_EVENT_SESSION_ADD,
522 .session_add = {
523 .session = session,
524 }
525 };
526
527 return context_raise(c, &event, 0);
528 }
529
530 static int context_raise_session_remove(sysview_context *c, sysview_session *session) {
531 sysview_event event = {
532 .type = SYSVIEW_EVENT_SESSION_REMOVE,
533 .session_remove = {
534 .session = session,
535 }
536 };
537
538 return context_raise(c, &event, 0);
539 }
540
541 static int context_raise_session_control(sysview_context *c, sysview_session *session, int error) {
542 sysview_event event = {
543 .type = SYSVIEW_EVENT_SESSION_CONTROL,
544 .session_control = {
545 .session = session,
546 .error = error,
547 }
548 };
549
550 return context_raise(c, &event, 0);
551 }
552
553 static int context_raise_session_attach(sysview_context *c, sysview_session *session, sysview_device *device) {
554 sysview_event event = {
555 .type = SYSVIEW_EVENT_SESSION_ATTACH,
556 .session_attach = {
557 .session = session,
558 .device = device,
559 }
560 };
561
562 return context_raise(c, &event, 0);
563 }
564
565 static int context_raise_session_detach(sysview_context *c, sysview_session *session, sysview_device *device) {
566 sysview_event event = {
567 .type = SYSVIEW_EVENT_SESSION_DETACH,
568 .session_detach = {
569 .session = session,
570 .device = device,
571 }
572 };
573
574 return context_raise(c, &event, 0);
575 }
576
577 static int context_raise_session_refresh(sysview_context *c, sysview_session *session, sysview_device *device, struct udev_device *ud) {
578 sysview_event event = {
579 .type = SYSVIEW_EVENT_SESSION_REFRESH,
580 .session_refresh = {
581 .session = session,
582 .device = device,
583 .ud = ud,
584 }
585 };
586
587 return context_raise(c, &event, 0);
588 }
589
590 static void context_add_device(sysview_context *c, sysview_device *device) {
591 sysview_session *session;
592 Iterator i;
593 int r;
594
595 assert(c);
596 assert(device);
597
598 log_debug("sysview: add device '%s' on seat '%s'",
599 device->name, device->seat->name);
600
601 HASHMAP_FOREACH(session, device->seat->session_map, i) {
602 if (!session->public)
603 continue;
604
605 r = context_raise_session_attach(c, session, device);
606 if (r < 0)
607 log_debug_errno(r, "sysview: callback failed while attaching device '%s' to session '%s': %m",
608 device->name, session->name);
609 }
610 }
611
612 static void context_remove_device(sysview_context *c, sysview_device *device) {
613 sysview_session *session;
614 Iterator i;
615 int r;
616
617 assert(c);
618 assert(device);
619
620 log_debug("sysview: remove device '%s'", device->name);
621
622 HASHMAP_FOREACH(session, device->seat->session_map, i) {
623 if (!session->public)
624 continue;
625
626 r = context_raise_session_detach(c, session, device);
627 if (r < 0)
628 log_debug_errno(r, "sysview: callback failed while detaching device '%s' from session '%s': %m",
629 device->name, session->name);
630 }
631
632 sysview_device_free(device);
633 }
634
635 static void context_change_device(sysview_context *c, sysview_device *device, struct udev_device *ud) {
636 sysview_session *session;
637 Iterator i;
638 int r;
639
640 assert(c);
641 assert(device);
642
643 log_debug("sysview: change device '%s'", device->name);
644
645 HASHMAP_FOREACH(session, device->seat->session_map, i) {
646 if (!session->public)
647 continue;
648
649 r = context_raise_session_refresh(c, session, device, ud);
650 if (r < 0)
651 log_debug_errno(r, "sysview: callback failed while changing device '%s' on session '%s': %m",
652 device->name, session->name);
653 }
654 }
655
656 static void context_add_session(sysview_context *c, sysview_seat *seat, const char *id) {
657 sysview_session *session;
658 sysview_device *device;
659 Iterator i;
660 int r;
661
662 assert(c);
663 assert(seat);
664 assert(id);
665
666 session = sysview_find_session(c, id);
667 if (session)
668 return;
669
670 log_debug("sysview: add session '%s' on seat '%s'", id, seat->name);
671
672 r = sysview_session_new(&session, seat, id);
673 if (r < 0)
674 goto error;
675
676 if (!seat->scanned) {
677 r = sysview_context_rescan(c);
678 if (r < 0)
679 goto error;
680 }
681
682 if (seat->public) {
683 session->public = true;
684 r = context_raise_session_add(c, session);
685 if (r < 0) {
686 log_debug_errno(r, "sysview: callback failed while adding session '%s': %m",
687 session->name);
688 session->public = false;
689 goto error;
690 }
691
692 HASHMAP_FOREACH(device, seat->device_map, i) {
693 r = context_raise_session_attach(c, session, device);
694 if (r < 0)
695 log_debug_errno(r, "sysview: callback failed while attaching device '%s' to new session '%s': %m",
696 device->name, session->name);
697 }
698 }
699
700 return;
701
702 error:
703 if (r < 0)
704 log_debug_errno(r, "sysview: error while adding session '%s': %m",
705 id);
706 }
707
708 static void context_remove_session(sysview_context *c, sysview_session *session) {
709 sysview_device *device;
710 Iterator i;
711 int r;
712
713 assert(c);
714 assert(session);
715
716 log_debug("sysview: remove session '%s'", session->name);
717
718 if (session->public) {
719 HASHMAP_FOREACH(device, session->seat->device_map, i) {
720 r = context_raise_session_detach(c, session, device);
721 if (r < 0)
722 log_debug_errno(r, "sysview: callback failed while detaching device '%s' from old session '%s': %m",
723 device->name, session->name);
724 }
725
726 session->public = false;
727 r = context_raise_session_remove(c, session);
728 if (r < 0)
729 log_debug_errno(r, "sysview: callback failed while removing session '%s': %m",
730 session->name);
731 }
732
733 if (!session->custom)
734 sysview_session_release_control(session);
735
736 sysview_session_free(session);
737 }
738
739 static void context_add_seat(sysview_context *c, const char *id) {
740 sysview_seat *seat;
741 int r;
742
743 assert(c);
744 assert(id);
745
746 seat = sysview_find_seat(c, id);
747 if (seat)
748 return;
749
750 log_debug("sysview: add seat '%s'", id);
751
752 r = sysview_seat_new(&seat, c, id);
753 if (r < 0)
754 goto error;
755
756 seat->public = true;
757 r = context_raise_seat_add(c, seat);
758 if (r < 0) {
759 log_debug_errno(r, "sysview: callback failed while adding seat '%s': %m",
760 seat->name);
761 seat->public = false;
762 }
763
764 return;
765
766 error:
767 if (r < 0)
768 log_debug_errno(r, "sysview: error while adding seat '%s': %m",
769 id);
770 }
771
772 static void context_remove_seat(sysview_context *c, sysview_seat *seat) {
773 sysview_session *session;
774 sysview_device *device;
775 int r;
776
777 assert(c);
778 assert(seat);
779
780 log_debug("sysview: remove seat '%s'", seat->name);
781
782 while ((device = hashmap_first(seat->device_map)))
783 context_remove_device(c, device);
784
785 while ((session = hashmap_first(seat->session_map)))
786 context_remove_session(c, session);
787
788 if (seat->public) {
789 seat->public = false;
790 r = context_raise_seat_remove(c, seat);
791 if (r < 0)
792 log_debug_errno(r, "sysview: callback failed while removing seat '%s': %m",
793 seat->name);
794 }
795
796 sysview_seat_free(seat);
797 }
798
799 int sysview_context_new(sysview_context **out,
800 unsigned int flags,
801 sd_event *event,
802 sd_bus *sysbus,
803 struct udev *ud) {
804 _cleanup_(sysview_context_freep) sysview_context *c = NULL;
805 int r;
806
807 assert_return(out, -EINVAL);
808 assert_return(event, -EINVAL);
809
810 log_debug("sysview: new");
811
812 c = new0(sysview_context, 1);
813 if (!c)
814 return -ENOMEM;
815
816 c->event = sd_event_ref(event);
817 if (flags & SYSVIEW_CONTEXT_SCAN_LOGIND)
818 c->scan_logind = true;
819 if (flags & SYSVIEW_CONTEXT_SCAN_EVDEV)
820 c->scan_evdev = true;
821 if (flags & SYSVIEW_CONTEXT_SCAN_DRM)
822 c->scan_drm = true;
823
824 if (sysbus) {
825 c->sysbus = sd_bus_ref(sysbus);
826 } else if (c->scan_logind) {
827 r = sd_bus_open_system(&c->sysbus);
828 if (r < 0)
829 return r;
830 }
831
832 if (ud) {
833 c->ud = udev_ref(ud);
834 } else if (c->scan_evdev || c->scan_drm) {
835 errno = 0;
836 c->ud = udev_new();
837 if (!c->ud)
838 return errno > 0 ? -errno : -EFAULT;
839 }
840
841 c->seat_map = hashmap_new(&string_hash_ops);
842 if (!c->seat_map)
843 return -ENOMEM;
844
845 c->session_map = hashmap_new(&string_hash_ops);
846 if (!c->session_map)
847 return -ENOMEM;
848
849 c->device_map = hashmap_new(&string_hash_ops);
850 if (!c->device_map)
851 return -ENOMEM;
852
853 *out = c;
854 c = NULL;
855 return 0;
856 }
857
858 sysview_context *sysview_context_free(sysview_context *c) {
859 if (!c)
860 return NULL;
861
862 log_debug("sysview: free");
863
864 sysview_context_stop(c);
865
866 assert(hashmap_size(c->device_map) == 0);
867 assert(hashmap_size(c->session_map) == 0);
868 assert(hashmap_size(c->seat_map) == 0);
869
870 hashmap_free(c->device_map);
871 hashmap_free(c->session_map);
872 hashmap_free(c->seat_map);
873 c->ud = udev_unref(c->ud);
874 c->sysbus = sd_bus_unref(c->sysbus);
875 c->event = sd_event_unref(c->event);
876 free(c);
877
878 return NULL;
879 }
880
881 static int context_ud_prepare_monitor(sysview_context *c, struct udev_monitor *m) {
882 int r;
883
884 if (c->scan_evdev) {
885 r = udev_monitor_filter_add_match_subsystem_devtype(m, "input", NULL);
886 if (r < 0)
887 return r;
888 }
889
890 if (c->scan_drm) {
891 r = udev_monitor_filter_add_match_subsystem_devtype(m, "drm", NULL);
892 if (r < 0)
893 return r;
894 }
895
896 return 0;
897 }
898
899 static int context_ud_prepare_scan(sysview_context *c, struct udev_enumerate *e) {
900 int r;
901
902 if (c->scan_evdev) {
903 r = udev_enumerate_add_match_subsystem(e, "input");
904 if (r < 0)
905 return r;
906 }
907
908 if (c->scan_drm) {
909 r = udev_enumerate_add_match_subsystem(e, "drm");
910 if (r < 0)
911 return r;
912 }
913
914 r = udev_enumerate_add_match_is_initialized(e);
915 if (r < 0)
916 return r;
917
918 return 0;
919 }
920
921 static int context_ud_hotplug(sysview_context *c, struct udev_device *d) {
922 const char *syspath, *sysname, *subsystem, *action, *seatname;
923 sysview_device *device;
924 int r;
925
926 syspath = udev_device_get_syspath(d);
927 sysname = udev_device_get_sysname(d);
928 subsystem = udev_device_get_subsystem(d);
929 action = udev_device_get_action(d);
930
931 /* not interested in custom devices without syspath/etc */
932 if (!syspath || !sysname || !subsystem)
933 return 0;
934
935 device = sysview_find_device(c, syspath);
936
937 if (streq_ptr(action, "remove")) {
938 if (!device)
939 return 0;
940
941 context_remove_device(c, device);
942 } else if (streq_ptr(action, "change")) {
943 if (!device)
944 return 0;
945
946 context_change_device(c, device, d);
947 } else if (!action || streq_ptr(action, "add")) {
948 struct udev_device *p;
949 unsigned int type, t;
950 sysview_seat *seat;
951
952 if (device)
953 return 0;
954
955 if (streq(subsystem, "input") && startswith(sysname, "event") && safe_atou(sysname + 5, &t) >= 0)
956 type = SYSVIEW_DEVICE_EVDEV;
957 else if (streq(subsystem, "drm") && startswith(sysname, "card"))
958 type = SYSVIEW_DEVICE_DRM;
959 else
960 type = (unsigned)-1;
961
962 if (type >= SYSVIEW_DEVICE_CNT)
963 return 0;
964
965 p = d;
966 seatname = NULL;
967 do {
968 seatname = udev_device_get_property_value(p, "ID_SEAT");
969 if (seatname)
970 break;
971 } while ((p = udev_device_get_parent(p)));
972
973 seat = sysview_find_seat(c, seatname ? : "seat0");
974 if (!seat)
975 return 0;
976
977 r = device_new_ud(&device, seat, type, d);
978 if (r < 0)
979 return log_debug_errno(r, "sysview: cannot create device for udev-device '%s': %m",
980 syspath);
981
982 context_add_device(c, device);
983 }
984
985 return 0;
986 }
987
988 static int context_ud_monitor_fn(sd_event_source *s,
989 int fd,
990 uint32_t revents,
991 void *userdata) {
992 sysview_context *c = userdata;
993 struct udev_device *d;
994 int r;
995
996 if (revents & EPOLLIN) {
997 while ((d = udev_monitor_receive_device(c->ud_monitor))) {
998 r = context_ud_hotplug(c, d);
999 udev_device_unref(d);
1000 if (r != 0)
1001 return r;
1002 }
1003
1004 /* as long as EPOLLIN is signalled, read pending data */
1005 return 0;
1006 }
1007
1008 if (revents & (EPOLLHUP | EPOLLERR)) {
1009 log_debug("sysview: HUP on udev-monitor");
1010 c->ud_monitor_src = sd_event_source_unref(c->ud_monitor_src);
1011 }
1012
1013 return 0;
1014 }
1015
1016 static int context_ud_start(sysview_context *c) {
1017 int r, fd;
1018
1019 if (!c->ud)
1020 return 0;
1021
1022 errno = 0;
1023 c->ud_monitor = udev_monitor_new_from_netlink(c->ud, "udev");
1024 if (!c->ud_monitor)
1025 return errno > 0 ? -errno : -EFAULT;
1026
1027 r = context_ud_prepare_monitor(c, c->ud_monitor);
1028 if (r < 0)
1029 return r;
1030
1031 r = udev_monitor_enable_receiving(c->ud_monitor);
1032 if (r < 0)
1033 return r;
1034
1035 fd = udev_monitor_get_fd(c->ud_monitor);
1036 r = sd_event_add_io(c->event,
1037 &c->ud_monitor_src,
1038 fd,
1039 EPOLLHUP | EPOLLERR | EPOLLIN,
1040 context_ud_monitor_fn,
1041 c);
1042 if (r < 0)
1043 return r;
1044
1045 return 0;
1046 }
1047
1048 static void context_ud_stop(sysview_context *c) {
1049 c->ud_monitor_src = sd_event_source_unref(c->ud_monitor_src);
1050 c->ud_monitor = udev_monitor_unref(c->ud_monitor);
1051 }
1052
1053 static int context_ud_scan(sysview_context *c) {
1054 _cleanup_(udev_enumerate_unrefp) struct udev_enumerate *e = NULL;
1055 struct udev_list_entry *entry;
1056 struct udev_device *d;
1057 int r;
1058
1059 if (!c->ud_monitor)
1060 return 0;
1061
1062 errno = 0;
1063 e = udev_enumerate_new(c->ud);
1064 if (!e)
1065 return errno > 0 ? -errno : -EFAULT;
1066
1067 r = context_ud_prepare_scan(c, e);
1068 if (r < 0)
1069 return r;
1070
1071 r = udev_enumerate_scan_devices(e);
1072 if (r < 0)
1073 return r;
1074
1075 udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) {
1076 const char *name;
1077
1078 name = udev_list_entry_get_name(entry);
1079
1080 errno = 0;
1081 d = udev_device_new_from_syspath(c->ud, name);
1082 if (!d) {
1083 r = errno > 0 ? -errno : -EFAULT;
1084 log_debug_errno(r, "sysview: cannot create udev-device for %s: %m",
1085 name);
1086 continue;
1087 }
1088
1089 r = context_ud_hotplug(c, d);
1090 udev_device_unref(d);
1091 if (r != 0)
1092 return r;
1093 }
1094
1095 return 0;
1096 }
1097
1098 static int context_ld_seat_new(sysview_context *c, sd_bus_message *signal) {
1099 const char *id, *path;
1100 int r;
1101
1102 r = sd_bus_message_read(signal, "so", &id, &path);
1103 if (r < 0)
1104 return log_debug_errno(r, "sysview: cannot parse SeatNew from logind: %m");
1105
1106 context_add_seat(c, id);
1107 return 0;
1108 }
1109
1110 static int context_ld_seat_removed(sysview_context *c, sd_bus_message *signal) {
1111 const char *id, *path;
1112 sysview_seat *seat;
1113 int r;
1114
1115 r = sd_bus_message_read(signal, "so", &id, &path);
1116 if (r < 0)
1117 return log_debug_errno(r, "sysview: cannot parse SeatRemoved from logind: %m");
1118
1119 seat = sysview_find_seat(c, id);
1120 if (!seat)
1121 return 0;
1122
1123 context_remove_seat(c, seat);
1124 return 0;
1125 }
1126
1127 static int context_ld_session_new(sysview_context *c, sd_bus_message *signal) {
1128 _cleanup_free_ char *seatid = NULL, *username = NULL;
1129 const char *id, *path;
1130 sysview_seat *seat;
1131 uid_t uid;
1132 int r;
1133
1134 r = sd_bus_message_read(signal, "so", &id, &path);
1135 if (r < 0)
1136 return log_debug_errno(r, "sysview: cannot parse SessionNew from logind: %m");
1137
1138 /*
1139 * As the dbus message didn't contain enough information, we
1140 * read missing bits via sd-login. Note that this might race session
1141 * destruction, so we handle ENOENT properly.
1142 */
1143
1144 /* ENOENT is also returned for sessions without seats */
1145 r = sd_session_get_seat(id, &seatid);
1146 if (r == -ENOENT)
1147 return 0;
1148 else if (r < 0)
1149 goto error;
1150
1151 seat = sysview_find_seat(c, seatid);
1152 if (!seat)
1153 return 0;
1154
1155 r = sd_session_get_uid(id, &uid);
1156 if (r == -ENOENT)
1157 return 0;
1158 else if (r < 0)
1159 goto error;
1160
1161 username = lookup_uid(uid);
1162 if (!username) {
1163 r = -ENOMEM;
1164 goto error;
1165 }
1166
1167 r = context_raise_session_filter(c, id, seatid, username, uid);
1168 if (r < 0)
1169 log_debug_errno(r, "sysview: callback failed while filtering session '%s': %m",
1170 id);
1171 else if (r > 0)
1172 context_add_session(c, seat, id);
1173
1174 return 0;
1175
1176 error:
1177 return log_debug_errno(r, "sysview: failed retrieving information for new session '%s': %m",
1178 id);
1179 }
1180
1181 static int context_ld_session_removed(sysview_context *c, sd_bus_message *signal) {
1182 sysview_session *session;
1183 const char *id, *path;
1184 int r;
1185
1186 r = sd_bus_message_read(signal, "so", &id, &path);
1187 if (r < 0)
1188 return log_debug_errno(r, "sysview: cannot parse SessionRemoved from logind: %m");
1189
1190 session = sysview_find_session(c, id);
1191 if (!session)
1192 return 0;
1193
1194 context_remove_session(c, session);
1195 return 0;
1196 }
1197
1198 static int context_ld_manager_signal_fn(sd_bus *bus,
1199 sd_bus_message *signal,
1200 void *userdata,
1201 sd_bus_error *ret_error) {
1202 sysview_context *c = userdata;
1203
1204 if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SeatNew"))
1205 return context_ld_seat_new(c, signal);
1206 else if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SeatRemoved"))
1207 return context_ld_seat_removed(c, signal);
1208 else if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SessionNew"))
1209 return context_ld_session_new(c, signal);
1210 else if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SessionRemoved"))
1211 return context_ld_session_removed(c, signal);
1212 else
1213 return 0;
1214 }
1215
1216 static int context_ld_start(sysview_context *c) {
1217 int r;
1218
1219 if (!c->scan_logind)
1220 return 0;
1221
1222 r = sd_bus_add_match(c->sysbus,
1223 &c->ld_slot_manager_signal,
1224 "type='signal',"
1225 "sender='org.freedesktop.login1',"
1226 "interface='org.freedesktop.login1.Manager',"
1227 "path='/org/freedesktop/login1'",
1228 context_ld_manager_signal_fn,
1229 c);
1230 if (r < 0)
1231 return r;
1232
1233 return 0;
1234 }
1235
1236 static void context_ld_stop(sysview_context *c) {
1237 c->ld_slot_list_sessions = sd_bus_slot_unref(c->ld_slot_list_sessions);
1238 c->ld_slot_list_seats = sd_bus_slot_unref(c->ld_slot_list_seats);
1239 c->ld_slot_manager_signal = sd_bus_slot_unref(c->ld_slot_manager_signal);
1240 }
1241
1242 static int context_ld_list_seats_fn(sd_bus *bus,
1243 sd_bus_message *reply,
1244 void *userdata,
1245 sd_bus_error *ret_error) {
1246 sysview_context *c = userdata;
1247 int r;
1248
1249 c->ld_slot_list_seats = sd_bus_slot_unref(c->ld_slot_list_seats);
1250
1251 if (sd_bus_message_is_method_error(reply, NULL)) {
1252 const sd_bus_error *error = sd_bus_message_get_error(reply);
1253
1254 log_debug("sysview: ListSeats on logind failed: %s: %s",
1255 error->name, error->message);
1256 return -sd_bus_error_get_errno(error);
1257 }
1258
1259 r = sd_bus_message_enter_container(reply, 'a', "(so)");
1260 if (r < 0)
1261 goto error;
1262
1263 while ((r = sd_bus_message_enter_container(reply, 'r', "so")) > 0) {
1264 const char *id, *path;
1265
1266 r = sd_bus_message_read(reply, "so", &id, &path);
1267 if (r < 0)
1268 goto error;
1269
1270 context_add_seat(c, id);
1271
1272 r = sd_bus_message_exit_container(reply);
1273 if (r < 0)
1274 goto error;
1275 }
1276
1277 if (r < 0)
1278 goto error;
1279
1280 r = sd_bus_message_exit_container(reply);
1281 if (r < 0)
1282 return r;
1283
1284 return 0;
1285
1286 error:
1287 return log_debug_errno(r, "sysview: erroneous ListSeats response from logind: %m");
1288 }
1289
1290 static int context_ld_list_sessions_fn(sd_bus *bus,
1291 sd_bus_message *reply,
1292 void *userdata,
1293 sd_bus_error *ret_error) {
1294 sysview_context *c = userdata;
1295 int r;
1296
1297 c->ld_slot_list_sessions = sd_bus_slot_unref(c->ld_slot_list_sessions);
1298
1299 if (sd_bus_message_is_method_error(reply, NULL)) {
1300 const sd_bus_error *error = sd_bus_message_get_error(reply);
1301
1302 log_debug("sysview: ListSessions on logind failed: %s: %s",
1303 error->name, error->message);
1304 return -sd_bus_error_get_errno(error);
1305 }
1306
1307 r = sd_bus_message_enter_container(reply, 'a', "(susso)");
1308 if (r < 0)
1309 goto error;
1310
1311 while ((r = sd_bus_message_enter_container(reply, 'r', "susso")) > 0) {
1312 const char *id, *username, *seatid, *path;
1313 sysview_seat *seat;
1314 unsigned int uid;
1315
1316 r = sd_bus_message_read(reply,
1317 "susso",
1318 &id,
1319 &uid,
1320 &username,
1321 &seatid,
1322 &path);
1323 if (r < 0)
1324 goto error;
1325
1326 seat = sysview_find_seat(c, seatid);
1327 if (seat) {
1328 r = context_raise_session_filter(c, id, seatid, username, uid);
1329 if (r < 0)
1330 log_debug_errno(r, "sysview: callback failed while filtering session '%s': %m",
1331 id);
1332 else if (r > 0)
1333 context_add_session(c, seat, id);
1334 }
1335
1336 r = sd_bus_message_exit_container(reply);
1337 if (r < 0)
1338 goto error;
1339 }
1340
1341 if (r < 0)
1342 goto error;
1343
1344 r = sd_bus_message_exit_container(reply);
1345 if (r < 0)
1346 return r;
1347
1348 return 0;
1349
1350 error:
1351 return log_debug_errno(r, "sysview: erroneous ListSessions response from logind: %m");
1352 }
1353
1354 static int context_ld_scan(sysview_context *c) {
1355 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
1356 int r;
1357
1358 if (!c->ld_slot_manager_signal)
1359 return 0;
1360
1361 /* request seat list */
1362
1363 r = sd_bus_message_new_method_call(c->sysbus,
1364 &m,
1365 "org.freedesktop.login1",
1366 "/org/freedesktop/login1",
1367 "org.freedesktop.login1.Manager",
1368 "ListSeats");
1369 if (r < 0)
1370 return r;
1371
1372 r = sd_bus_call_async(c->sysbus,
1373 &c->ld_slot_list_seats,
1374 m,
1375 context_ld_list_seats_fn,
1376 c,
1377 0);
1378 if (r < 0)
1379 return r;
1380
1381 /* request session list */
1382
1383 m = sd_bus_message_unref(m);
1384 r = sd_bus_message_new_method_call(c->sysbus,
1385 &m,
1386 "org.freedesktop.login1",
1387 "/org/freedesktop/login1",
1388 "org.freedesktop.login1.Manager",
1389 "ListSessions");
1390 if (r < 0)
1391 return r;
1392
1393 r = sd_bus_call_async(c->sysbus,
1394 &c->ld_slot_list_sessions,
1395 m,
1396 context_ld_list_sessions_fn,
1397 c,
1398 0);
1399 if (r < 0)
1400 return r;
1401
1402 return 0;
1403 }
1404
1405 bool sysview_context_is_running(sysview_context *c) {
1406 return c && c->running;
1407 }
1408
1409 int sysview_context_start(sysview_context *c, sysview_event_fn event_fn, void *userdata) {
1410 int r;
1411
1412 assert_return(c, -EINVAL);
1413 assert_return(event_fn, -EINVAL);
1414
1415 if (c->running)
1416 return -EALREADY;
1417
1418 log_debug("sysview: start");
1419
1420 c->running = true;
1421 c->event_fn = event_fn;
1422 c->userdata = userdata;
1423
1424 r = context_ld_start(c);
1425 if (r < 0)
1426 goto error;
1427
1428 r = context_ud_start(c);
1429 if (r < 0)
1430 goto error;
1431
1432 r = sysview_context_rescan(c);
1433 if (r < 0)
1434 goto error;
1435
1436 return 0;
1437
1438 error:
1439 sysview_context_stop(c);
1440 return r;
1441 }
1442
1443 void sysview_context_stop(sysview_context *c) {
1444 sysview_session *session;
1445 sysview_device *device;
1446 sysview_seat *seat;
1447
1448 assert(c);
1449
1450 if (!c->running)
1451 return;
1452
1453 log_debug("sysview: stop");
1454
1455 while ((device = hashmap_first(c->device_map)))
1456 context_remove_device(c, device);
1457
1458 while ((session = hashmap_first(c->session_map)))
1459 context_remove_session(c, session);
1460
1461 while ((seat = hashmap_first(c->seat_map)))
1462 context_remove_seat(c, seat);
1463
1464 c->running = false;
1465 c->scanned = false;
1466 c->event_fn = NULL;
1467 c->userdata = NULL;
1468 c->scan_src = sd_event_source_unref(c->scan_src);
1469 context_ud_stop(c);
1470 context_ld_stop(c);
1471 }
1472
1473 static int context_scan_fn(sd_event_source *s, void *userdata) {
1474 sysview_context *c = userdata;
1475 sysview_seat *seat;
1476 Iterator i;
1477 int r;
1478
1479 if (!c->scanned) {
1480 r = context_ld_scan(c);
1481 if (r < 0)
1482 return log_debug_errno(r, "sysview: logind scan failed: %m");
1483 }
1484
1485 /* skip device scans if no sessions are available */
1486 if (hashmap_size(c->session_map) > 0) {
1487 r = context_ud_scan(c);
1488 if (r < 0)
1489 return log_debug_errno(r, "sysview: udev scan failed: %m");
1490
1491 HASHMAP_FOREACH(seat, c->seat_map, i)
1492 seat->scanned = true;
1493 }
1494
1495 c->scanned = true;
1496
1497 return 0;
1498 }
1499
1500 int sysview_context_rescan(sysview_context *c) {
1501 assert(c);
1502
1503 if (!c->running)
1504 return 0;
1505
1506 if (c->scan_src)
1507 return sd_event_source_set_enabled(c->scan_src, SD_EVENT_ONESHOT);
1508 else
1509 return sd_event_add_defer(c->event, &c->scan_src, context_scan_fn, c);
1510 }