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