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