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