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