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