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