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