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