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