chiark / gitweb /
treewide: simplify log_*_errno(r,...) immediately followed by "return r"
[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                         return log_debug_errno(r, "sysview: cannot create device for udev-device '%s': %m",
980                                                syspath);
981
982                 context_add_device(c, device);
983         }
984
985         return 0;
986 }
987
988 static int context_ud_monitor_fn(sd_event_source *s,
989                                  int fd,
990                                  uint32_t revents,
991                                  void *userdata) {
992         sysview_context *c = userdata;
993         struct udev_device *d;
994         int r;
995
996         if (revents & EPOLLIN) {
997                 while ((d = udev_monitor_receive_device(c->ud_monitor))) {
998                         r = context_ud_hotplug(c, d);
999                         udev_device_unref(d);
1000                         if (r != 0)
1001                                 return r;
1002                 }
1003
1004                 /* as long as EPOLLIN is signalled, read pending data */
1005                 return 0;
1006         }
1007
1008         if (revents & (EPOLLHUP | EPOLLERR)) {
1009                 log_debug("sysview: HUP on udev-monitor");
1010                 c->ud_monitor_src = sd_event_source_unref(c->ud_monitor_src);
1011         }
1012
1013         return 0;
1014 }
1015
1016 static int context_ud_start(sysview_context *c) {
1017         int r, fd;
1018
1019         if (!c->ud)
1020                 return 0;
1021
1022         errno = 0;
1023         c->ud_monitor = udev_monitor_new_from_netlink(c->ud, "udev");
1024         if (!c->ud_monitor)
1025                 return errno > 0 ? -errno : -EFAULT;
1026
1027         r = context_ud_prepare_monitor(c, c->ud_monitor);
1028         if (r < 0)
1029                 return r;
1030
1031         r = udev_monitor_enable_receiving(c->ud_monitor);
1032         if (r < 0)
1033                 return r;
1034
1035         fd = udev_monitor_get_fd(c->ud_monitor);
1036         r = sd_event_add_io(c->event,
1037                             &c->ud_monitor_src,
1038                             fd,
1039                             EPOLLHUP | EPOLLERR | EPOLLIN,
1040                             context_ud_monitor_fn,
1041                             c);
1042         if (r < 0)
1043                 return r;
1044
1045         return 0;
1046 }
1047
1048 static void context_ud_stop(sysview_context *c) {
1049         c->ud_monitor_src = sd_event_source_unref(c->ud_monitor_src);
1050         c->ud_monitor = udev_monitor_unref(c->ud_monitor);
1051 }
1052
1053 static int context_ud_scan(sysview_context *c) {
1054         _cleanup_(udev_enumerate_unrefp) struct udev_enumerate *e = NULL;
1055         struct udev_list_entry *entry;
1056         struct udev_device *d;
1057         int r;
1058
1059         if (!c->ud_monitor)
1060                 return 0;
1061
1062         errno = 0;
1063         e = udev_enumerate_new(c->ud);
1064         if (!e)
1065                 return errno > 0 ? -errno : -EFAULT;
1066
1067         r = context_ud_prepare_scan(c, e);
1068         if (r < 0)
1069                 return r;
1070
1071         r = udev_enumerate_scan_devices(e);
1072         if (r < 0)
1073                 return r;
1074
1075         udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) {
1076                 const char *name;
1077
1078                 name = udev_list_entry_get_name(entry);
1079
1080                 errno = 0;
1081                 d = udev_device_new_from_syspath(c->ud, name);
1082                 if (!d) {
1083                         r = errno > 0 ? -errno : -EFAULT;
1084                         log_debug_errno(r, "sysview: cannot create udev-device for %s: %m",
1085                                         name);
1086                         continue;
1087                 }
1088
1089                 r = context_ud_hotplug(c, d);
1090                 udev_device_unref(d);
1091                 if (r != 0)
1092                         return r;
1093         }
1094
1095         return 0;
1096 }
1097
1098 static int context_ld_seat_new(sysview_context *c, sd_bus_message *signal) {
1099         const char *id, *path;
1100         int r;
1101
1102         r = sd_bus_message_read(signal, "so", &id, &path);
1103         if (r < 0)
1104                 return log_debug_errno(r, "sysview: cannot parse SeatNew from logind: %m");
1105
1106         context_add_seat(c, id);
1107         return 0;
1108 }
1109
1110 static int context_ld_seat_removed(sysview_context *c, sd_bus_message *signal) {
1111         const char *id, *path;
1112         sysview_seat *seat;
1113         int r;
1114
1115         r = sd_bus_message_read(signal, "so", &id, &path);
1116         if (r < 0)
1117                 return log_debug_errno(r, "sysview: cannot parse SeatRemoved from logind: %m");
1118
1119         seat = sysview_find_seat(c, id);
1120         if (!seat)
1121                 return 0;
1122
1123         context_remove_seat(c, seat);
1124         return 0;
1125 }
1126
1127 static int context_ld_session_new(sysview_context *c, sd_bus_message *signal) {
1128         _cleanup_free_ char *seatid = NULL, *username = NULL;
1129         const char *id, *path;
1130         sysview_seat *seat;
1131         uid_t uid;
1132         int r;
1133
1134         r = sd_bus_message_read(signal, "so", &id, &path);
1135         if (r < 0)
1136                 return log_debug_errno(r, "sysview: cannot parse SessionNew from logind: %m");
1137
1138         /*
1139          * As the dbus message didn't contain enough information, we
1140          * read missing bits via sd-login. Note that this might race session
1141          * destruction, so we handle ENOENT properly.
1142          */
1143
1144         /* ENOENT is also returned for sessions without seats */
1145         r = sd_session_get_seat(id, &seatid);
1146         if (r == -ENOENT)
1147                 return 0;
1148         else if (r < 0)
1149                 goto error;
1150
1151         seat = sysview_find_seat(c, seatid);
1152         if (!seat)
1153                 return 0;
1154
1155         r = sd_session_get_uid(id, &uid);
1156         if (r == -ENOENT)
1157                 return 0;
1158         else if (r < 0)
1159                 goto error;
1160
1161         username = lookup_uid(uid);
1162         if (!username) {
1163                 r = -ENOMEM;
1164                 goto error;
1165         }
1166
1167         r = context_raise_session_filter(c, id, seatid, username, uid);
1168         if (r < 0)
1169                 log_debug_errno(r, "sysview: callback failed while filtering session '%s': %m",
1170                                 id);
1171         else if (r > 0)
1172                 context_add_session(c, seat, id);
1173
1174         return 0;
1175
1176 error:
1177         return log_debug_errno(r, "sysview: failed retrieving information for new session '%s': %m",
1178                                id);
1179 }
1180
1181 static int context_ld_session_removed(sysview_context *c, sd_bus_message *signal) {
1182         sysview_session *session;
1183         const char *id, *path;
1184         int r;
1185
1186         r = sd_bus_message_read(signal, "so", &id, &path);
1187         if (r < 0)
1188                 return log_debug_errno(r, "sysview: cannot parse SessionRemoved from logind: %m");
1189
1190         session = sysview_find_session(c, id);
1191         if (!session)
1192                 return 0;
1193
1194         context_remove_session(c, session);
1195         return 0;
1196 }
1197
1198 static int context_ld_manager_signal_fn(sd_bus *bus,
1199                                         sd_bus_message *signal,
1200                                         void *userdata,
1201                                         sd_bus_error *ret_error) {
1202         sysview_context *c = userdata;
1203
1204         if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SeatNew"))
1205                 return context_ld_seat_new(c, signal);
1206         else if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SeatRemoved"))
1207                 return context_ld_seat_removed(c, signal);
1208         else if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SessionNew"))
1209                 return context_ld_session_new(c, signal);
1210         else if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SessionRemoved"))
1211                 return context_ld_session_removed(c, signal);
1212         else
1213                 return 0;
1214 }
1215
1216 static int context_ld_start(sysview_context *c) {
1217         int r;
1218
1219         if (!c->scan_logind)
1220                 return 0;
1221
1222         r = sd_bus_add_match(c->sysbus,
1223                              &c->ld_slot_manager_signal,
1224                              "type='signal',"
1225                              "sender='org.freedesktop.login1',"
1226                              "interface='org.freedesktop.login1.Manager',"
1227                              "path='/org/freedesktop/login1'",
1228                              context_ld_manager_signal_fn,
1229                              c);
1230         if (r < 0)
1231                 return r;
1232
1233         return 0;
1234 }
1235
1236 static void context_ld_stop(sysview_context *c) {
1237         c->ld_slot_list_sessions = sd_bus_slot_unref(c->ld_slot_list_sessions);
1238         c->ld_slot_list_seats = sd_bus_slot_unref(c->ld_slot_list_seats);
1239         c->ld_slot_manager_signal = sd_bus_slot_unref(c->ld_slot_manager_signal);
1240 }
1241
1242 static int context_ld_list_seats_fn(sd_bus *bus,
1243                                     sd_bus_message *reply,
1244                                     void *userdata,
1245                                     sd_bus_error *ret_error) {
1246         sysview_context *c = userdata;
1247         int r;
1248
1249         c->ld_slot_list_seats = sd_bus_slot_unref(c->ld_slot_list_seats);
1250
1251         if (sd_bus_message_is_method_error(reply, NULL)) {
1252                 const sd_bus_error *error = sd_bus_message_get_error(reply);
1253
1254                 log_debug("sysview: ListSeats on logind failed: %s: %s",
1255                           error->name, error->message);
1256                 return -sd_bus_error_get_errno(error);
1257         }
1258
1259         r = sd_bus_message_enter_container(reply, 'a', "(so)");
1260         if (r < 0)
1261                 goto error;
1262
1263         while ((r = sd_bus_message_enter_container(reply, 'r', "so")) > 0) {
1264                 const char *id, *path;
1265
1266                 r = sd_bus_message_read(reply, "so", &id, &path);
1267                 if (r < 0)
1268                         goto error;
1269
1270                 context_add_seat(c, id);
1271
1272                 r = sd_bus_message_exit_container(reply);
1273                 if (r < 0)
1274                         goto error;
1275         }
1276
1277         if (r < 0)
1278                 goto error;
1279
1280         r = sd_bus_message_exit_container(reply);
1281         if (r < 0)
1282                 return r;
1283
1284         return 0;
1285
1286 error:
1287         return log_debug_errno(r, "sysview: erroneous ListSeats response from logind: %m");
1288 }
1289
1290 static int context_ld_list_sessions_fn(sd_bus *bus,
1291                                        sd_bus_message *reply,
1292                                        void *userdata,
1293                                        sd_bus_error *ret_error) {
1294         sysview_context *c = userdata;
1295         int r;
1296
1297         c->ld_slot_list_sessions = sd_bus_slot_unref(c->ld_slot_list_sessions);
1298
1299         if (sd_bus_message_is_method_error(reply, NULL)) {
1300                 const sd_bus_error *error = sd_bus_message_get_error(reply);
1301
1302                 log_debug("sysview: ListSessions on logind failed: %s: %s",
1303                           error->name, error->message);
1304                 return -sd_bus_error_get_errno(error);
1305         }
1306
1307         r = sd_bus_message_enter_container(reply, 'a', "(susso)");
1308         if (r < 0)
1309                 goto error;
1310
1311         while ((r = sd_bus_message_enter_container(reply, 'r', "susso")) > 0) {
1312                 const char *id, *username, *seatid, *path;
1313                 sysview_seat *seat;
1314                 unsigned int uid;
1315
1316                 r = sd_bus_message_read(reply,
1317                                         "susso",
1318                                         &id,
1319                                         &uid,
1320                                         &username,
1321                                         &seatid,
1322                                         &path);
1323                 if (r < 0)
1324                         goto error;
1325
1326                 seat = sysview_find_seat(c, seatid);
1327                 if (seat) {
1328                         r = context_raise_session_filter(c, id, seatid, username, uid);
1329                         if (r < 0)
1330                                 log_debug_errno(r, "sysview: callback failed while filtering session '%s': %m",
1331                                                 id);
1332                         else if (r > 0)
1333                                 context_add_session(c, seat, id);
1334                 }
1335
1336                 r = sd_bus_message_exit_container(reply);
1337                 if (r < 0)
1338                         goto error;
1339         }
1340
1341         if (r < 0)
1342                 goto error;
1343
1344         r = sd_bus_message_exit_container(reply);
1345         if (r < 0)
1346                 return r;
1347
1348         return 0;
1349
1350 error:
1351         return log_debug_errno(r, "sysview: erroneous ListSessions response from logind: %m");
1352 }
1353
1354 static int context_ld_scan(sysview_context *c) {
1355         _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
1356         int r;
1357
1358         if (!c->ld_slot_manager_signal)
1359                 return 0;
1360
1361         /* request seat list */
1362
1363         r = sd_bus_message_new_method_call(c->sysbus,
1364                                            &m,
1365                                            "org.freedesktop.login1",
1366                                            "/org/freedesktop/login1",
1367                                            "org.freedesktop.login1.Manager",
1368                                            "ListSeats");
1369         if (r < 0)
1370                 return r;
1371
1372         r = sd_bus_call_async(c->sysbus,
1373                               &c->ld_slot_list_seats,
1374                               m,
1375                               context_ld_list_seats_fn,
1376                               c,
1377                               0);
1378         if (r < 0)
1379                 return r;
1380
1381         /* request session list */
1382
1383         m = sd_bus_message_unref(m);
1384         r = sd_bus_message_new_method_call(c->sysbus,
1385                                            &m,
1386                                            "org.freedesktop.login1",
1387                                            "/org/freedesktop/login1",
1388                                            "org.freedesktop.login1.Manager",
1389                                            "ListSessions");
1390         if (r < 0)
1391                 return r;
1392
1393         r = sd_bus_call_async(c->sysbus,
1394                               &c->ld_slot_list_sessions,
1395                               m,
1396                               context_ld_list_sessions_fn,
1397                               c,
1398                               0);
1399         if (r < 0)
1400                 return r;
1401
1402         return 0;
1403 }
1404
1405 bool sysview_context_is_running(sysview_context *c) {
1406         return c && c->running;
1407 }
1408
1409 int sysview_context_start(sysview_context *c, sysview_event_fn event_fn, void *userdata) {
1410         int r;
1411
1412         assert_return(c, -EINVAL);
1413         assert_return(event_fn, -EINVAL);
1414
1415         if (c->running)
1416                 return -EALREADY;
1417
1418         log_debug("sysview: start");
1419
1420         c->running = true;
1421         c->event_fn = event_fn;
1422         c->userdata = userdata;
1423
1424         r = context_ld_start(c);
1425         if (r < 0)
1426                 goto error;
1427
1428         r = context_ud_start(c);
1429         if (r < 0)
1430                 goto error;
1431
1432         r = sysview_context_rescan(c);
1433         if (r < 0)
1434                 goto error;
1435
1436         return 0;
1437
1438 error:
1439         sysview_context_stop(c);
1440         return r;
1441 }
1442
1443 void sysview_context_stop(sysview_context *c) {
1444         sysview_session *session;
1445         sysview_device *device;
1446         sysview_seat *seat;
1447
1448         assert(c);
1449
1450         if (!c->running)
1451                 return;
1452
1453         log_debug("sysview: stop");
1454
1455         while ((device = hashmap_first(c->device_map)))
1456                 context_remove_device(c, device);
1457
1458         while ((session = hashmap_first(c->session_map)))
1459                 context_remove_session(c, session);
1460
1461         while ((seat = hashmap_first(c->seat_map)))
1462                 context_remove_seat(c, seat);
1463
1464         c->running = false;
1465         c->scanned = false;
1466         c->event_fn = NULL;
1467         c->userdata = NULL;
1468         c->scan_src = sd_event_source_unref(c->scan_src);
1469         context_ud_stop(c);
1470         context_ld_stop(c);
1471 }
1472
1473 static int context_scan_fn(sd_event_source *s, void *userdata) {
1474         sysview_context *c = userdata;
1475         sysview_seat *seat;
1476         Iterator i;
1477         int r;
1478
1479         if (!c->scanned) {
1480                 r = context_ld_scan(c);
1481                 if (r < 0)
1482                         return log_debug_errno(r, "sysview: logind scan failed: %m");
1483         }
1484
1485         /* skip device scans if no sessions are available */
1486         if (hashmap_size(c->session_map) > 0) {
1487                 r = context_ud_scan(c);
1488                 if (r < 0)
1489                         return log_debug_errno(r, "sysview: udev scan failed: %m");
1490
1491                 HASHMAP_FOREACH(seat, c->seat_map, i)
1492                         seat->scanned = true;
1493         }
1494
1495         c->scanned = true;
1496
1497         return 0;
1498 }
1499
1500 int sysview_context_rescan(sysview_context *c) {
1501         assert(c);
1502
1503         if (!c->running)
1504                 return 0;
1505
1506         if (c->scan_src)
1507                 return sd_event_source_set_enabled(c->scan_src, SD_EVENT_ONESHOT);
1508         else
1509                 return sd_event_add_defer(c->event, &c->scan_src, context_scan_fn, c);
1510 }