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