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