chiark / gitweb /
168c6c5ac721e0cbe7e2d167196f31ef5440b561
[elogind.git] / src / libelogind / sd-bus / bus-track.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3   This file is part of systemd.
4
5   Copyright 2013 Lennart Poettering
6 ***/
7
8 #include "sd-bus.h"
9
10 #include "alloc-util.h"
11 #include "bus-internal.h"
12 #include "bus-track.h"
13 #include "bus-util.h"
14
15 struct track_item {
16         unsigned n_ref;
17         char *name;
18         sd_bus_slot *slot;
19 };
20
21 struct sd_bus_track {
22         unsigned n_ref;
23         unsigned n_adding; /* are we in the process of adding a new name? */
24         sd_bus *bus;
25         sd_bus_track_handler_t handler;
26         void *userdata;
27         Hashmap *names;
28         LIST_FIELDS(sd_bus_track, queue);
29         Iterator iterator;
30         bool in_list:1;    /* In bus->tracks? */
31         bool in_queue:1;   /* In bus->track_queue? */
32         bool modified:1;
33         bool recursive:1;
34
35         LIST_FIELDS(sd_bus_track, tracks);
36 };
37
38 #define MATCH_FOR_NAME(name)                            \
39         strjoina("type='signal',"                       \
40                  "sender='org.freedesktop.DBus',"       \
41                  "path='/org/freedesktop/DBus',"        \
42                  "interface='org.freedesktop.DBus',"    \
43                  "member='NameOwnerChanged',"           \
44                  "arg0='", name, "'")
45
46 static struct track_item* track_item_free(struct track_item *i) {
47
48         if (!i)
49                 return NULL;
50
51         sd_bus_slot_unref(i->slot);
52         free(i->name);
53         return mfree(i);
54 }
55
56 DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item*, track_item_free);
57
58 static void bus_track_add_to_queue(sd_bus_track *track) {
59         assert(track);
60
61         /* Adds the bus track object to the queue of objects we should dispatch next, subject to a number of
62          * conditions. */
63
64         /* Already in the queue? */
65         if (track->in_queue)
66                 return;
67
68         /* if we are currently in the process of adding a new name, then let's not enqueue this just yet, let's wait
69          * until the addition is complete. */
70         if (track->n_adding > 0)
71                 return;
72
73         /* still referenced? */
74         if (hashmap_size(track->names) > 0)
75                 return;
76
77         /* Nothing to call? */
78         if (!track->handler)
79                 return;
80
81         /* Already closed? */
82         if (!track->in_list)
83                 return;
84
85         LIST_PREPEND(queue, track->bus->track_queue, track);
86         track->in_queue = true;
87 }
88
89 static void bus_track_remove_from_queue(sd_bus_track *track) {
90         assert(track);
91
92         if (!track->in_queue)
93                 return;
94
95         LIST_REMOVE(queue, track->bus->track_queue, track);
96         track->in_queue = false;
97 }
98
99 static int bus_track_remove_name_fully(sd_bus_track *track, const char *name) {
100         struct track_item *i;
101
102         assert(track);
103         assert(name);
104
105         i = hashmap_remove(track->names, name);
106         if (!i)
107                 return 0;
108
109         track_item_free(i);
110
111         bus_track_add_to_queue(track);
112
113         track->modified = true;
114         return 1;
115 }
116
117 _public_ int sd_bus_track_new(
118                 sd_bus *bus,
119                 sd_bus_track **track,
120                 sd_bus_track_handler_t handler,
121                 void *userdata) {
122
123         sd_bus_track *t;
124
125         assert_return(bus, -EINVAL);
126         assert_return(bus = bus_resolve(bus), -ENOPKG);
127         assert_return(track, -EINVAL);
128
129         if (!bus->bus_client)
130                 return -EINVAL;
131
132         t = new0(sd_bus_track, 1);
133         if (!t)
134                 return -ENOMEM;
135
136         t->n_ref = 1;
137         t->handler = handler;
138         t->userdata = userdata;
139         t->bus = sd_bus_ref(bus);
140
141         LIST_PREPEND(tracks, bus->tracks, t);
142         t->in_list = true;
143
144         bus_track_add_to_queue(t);
145
146         *track = t;
147         return 0;
148 }
149
150 _public_ sd_bus_track* sd_bus_track_ref(sd_bus_track *track) {
151
152         if (!track)
153                 return NULL;
154
155         assert(track->n_ref > 0);
156
157         track->n_ref++;
158
159         return track;
160 }
161
162 _public_ sd_bus_track* sd_bus_track_unref(sd_bus_track *track) {
163         if (!track)
164                 return NULL;
165
166         assert(track->n_ref > 0);
167
168         if (track->n_ref > 1) {
169                 track->n_ref--;
170                 return NULL;
171         }
172
173         if (track->in_list)
174                 LIST_REMOVE(tracks, track->bus->tracks, track);
175
176         bus_track_remove_from_queue(track);
177         hashmap_free_with_destructor(track->names, track_item_free);
178         sd_bus_unref(track->bus);
179         return mfree(track);
180 }
181
182 static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
183         sd_bus_track *track = userdata;
184         const char *name, *old, *new;
185         int r;
186
187         assert(message);
188         assert(track);
189
190         r = sd_bus_message_read(message, "sss", &name, &old, &new);
191         if (r < 0)
192                 return 0;
193
194         bus_track_remove_name_fully(track, name);
195         return 0;
196 }
197
198 _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) {
199         _cleanup_(track_item_freep) struct track_item *n = NULL;
200         struct track_item *i;
201         const char *match;
202         int r;
203
204         assert_return(track, -EINVAL);
205         assert_return(service_name_is_valid(name), -EINVAL);
206
207         i = hashmap_get(track->names, name);
208         if (i) {
209                 if (track->recursive) {
210                         unsigned k = track->n_ref + 1;
211
212                         if (k < track->n_ref) /* Check for overflow */
213                                 return -EOVERFLOW;
214
215                         track->n_ref = k;
216                 }
217
218                 bus_track_remove_from_queue(track);
219                 return 0;
220         }
221
222         r = hashmap_ensure_allocated(&track->names, &string_hash_ops);
223         if (r < 0)
224                 return r;
225
226         n = new0(struct track_item, 1);
227         if (!n)
228                 return -ENOMEM;
229         n->name = strdup(name);
230         if (!n->name)
231                 return -ENOMEM;
232
233         /* First, subscribe to this name */
234         match = MATCH_FOR_NAME(name);
235
236         bus_track_remove_from_queue(track); /* don't dispatch this while we work in it */
237
238         r = sd_bus_add_match_async(track->bus, &n->slot, match, on_name_owner_changed, NULL, track);
239         if (r < 0) {
240                 bus_track_add_to_queue(track);
241                 return r;
242         }
243
244         r = hashmap_put(track->names, n->name, n);
245         if (r < 0) {
246                 bus_track_add_to_queue(track);
247                 return r;
248         }
249
250         /* Second, check if it is currently existing, or maybe doesn't, or maybe disappeared already. */
251         track->n_adding++; /* again, make sure this isn't dispatch while we are working in it */
252         r = sd_bus_get_name_creds(track->bus, name, 0, NULL);
253         track->n_adding--;
254         if (r < 0) {
255                 hashmap_remove(track->names, name);
256                 bus_track_add_to_queue(track);
257                 return r;
258         }
259
260         n->n_ref = 1;
261         n = NULL;
262
263         bus_track_remove_from_queue(track);
264         track->modified = true;
265
266         return 1;
267 }
268
269 _public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) {
270         struct track_item *i;
271
272         assert_return(name, -EINVAL);
273
274         if (!track) /* Treat a NULL track object as an empty track object */
275                 return 0;
276
277         if (!track->recursive)
278                 return bus_track_remove_name_fully(track, name);
279
280         i = hashmap_get(track->names, name);
281         if (!i)
282                 return -EUNATCH;
283         if (i->n_ref <= 0)
284                 return -EUNATCH;
285
286         i->n_ref--;
287
288         if (i->n_ref <= 0)
289                 return bus_track_remove_name_fully(track, name);
290
291         return 1;
292 }
293
294 _public_ unsigned sd_bus_track_count(sd_bus_track *track) {
295
296         if (!track) /* Let's consider a NULL object equivalent to an empty object */
297                 return 0;
298
299         /* This signature really should have returned an int, so that we can propagate errors. But well, ... Also, note
300          * that this returns the number of names being watched, and multiple references to the same name are not
301          * counted. */
302
303         return hashmap_size(track->names);
304 }
305
306 _public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) {
307         assert_return(name, NULL);
308
309         if (!track) /* Let's consider a NULL object equivalent to an empty object */
310                 return NULL;
311
312         return hashmap_get(track->names, (void*) name) ? name : NULL;
313 }
314
315 _public_ const char* sd_bus_track_first(sd_bus_track *track) {
316         const char *n = NULL;
317
318         if (!track)
319                 return NULL;
320
321         track->modified = false;
322         track->iterator = ITERATOR_FIRST;
323
324         hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
325         return n;
326 }
327
328 _public_ const char* sd_bus_track_next(sd_bus_track *track) {
329         const char *n = NULL;
330
331         if (!track)
332                 return NULL;
333
334         if (track->modified)
335                 return NULL;
336
337         hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
338         return n;
339 }
340
341 _public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) {
342         const char *sender;
343
344         assert_return(track, -EINVAL);
345         assert_return(m, -EINVAL);
346
347         if (sd_bus_message_get_bus(m) != track->bus)
348                 return -EINVAL;
349
350         sender = sd_bus_message_get_sender(m);
351         if (!sender)
352                 return -EINVAL;
353
354         return sd_bus_track_add_name(track, sender);
355 }
356
357 _public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) {
358         const char *sender;
359
360         assert_return(m, -EINVAL);
361
362         if (!track) /* Treat a NULL track object as an empty track object */
363                 return 0;
364
365         if (sd_bus_message_get_bus(m) != track->bus)
366                 return -EINVAL;
367
368         sender = sd_bus_message_get_sender(m);
369         if (!sender)
370                 return -EINVAL;
371
372         return sd_bus_track_remove_name(track, sender);
373 }
374
375 _public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) {
376         assert_return(track, NULL);
377
378         return track->bus;
379 }
380
381 void bus_track_dispatch(sd_bus_track *track) {
382         int r;
383
384         assert(track);
385         assert(track->handler);
386
387         bus_track_remove_from_queue(track);
388
389         sd_bus_track_ref(track);
390
391         r = track->handler(track, track->userdata);
392         if (r < 0)
393                 log_debug_errno(r, "Failed to process track handler: %m");
394         else if (r == 0)
395                 bus_track_add_to_queue(track);
396
397         sd_bus_track_unref(track);
398 }
399
400 void bus_track_close(sd_bus_track *track) {
401         assert(track);
402
403         /* Called whenever our bus connected is closed. If so, and our track object is non-empty, dispatch it
404          * immediately, as we are closing now, but first flush out all names. */
405
406         if (!track->in_list)
407                 return; /* We already closed this one, don't close it again. */
408
409         /* Remember that this one is closed now */
410         LIST_REMOVE(tracks, track->bus->tracks, track);
411         track->in_list = false;
412
413         /* If there's no name in this one anyway, we don't have to dispatch */
414         if (hashmap_isempty(track->names))
415                 return;
416
417         /* Let's flush out all names */
418         hashmap_clear_with_destructor(track->names, track_item_free);
419
420         /* Invoke handler */
421         if (track->handler)
422                 bus_track_dispatch(track);
423 }
424
425 _public_ void *sd_bus_track_get_userdata(sd_bus_track *track) {
426         assert_return(track, NULL);
427
428         return track->userdata;
429 }
430
431 _public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) {
432         void *ret;
433
434         assert_return(track, NULL);
435
436         ret = track->userdata;
437         track->userdata = userdata;
438
439         return ret;
440 }
441
442 _public_ int sd_bus_track_set_recursive(sd_bus_track *track, int b) {
443         assert_return(track, -EINVAL);
444
445         if (track->recursive == !!b)
446                 return 0;
447
448         if (!hashmap_isempty(track->names))
449                 return -EBUSY;
450
451         track->recursive = b;
452         return 0;
453 }
454
455 _public_ int sd_bus_track_get_recursive(sd_bus_track *track) {
456         assert_return(track, -EINVAL);
457
458         return track->recursive;
459 }
460
461 _public_ int sd_bus_track_count_sender(sd_bus_track *track, sd_bus_message *m) {
462         const char *sender;
463
464         assert_return(m, -EINVAL);
465
466         if (!track) /* Let's consider a NULL object equivalent to an empty object */
467                 return 0;
468
469         if (sd_bus_message_get_bus(m) != track->bus)
470                 return -EINVAL;
471
472         sender = sd_bus_message_get_sender(m);
473         if (!sender)
474                 return -EINVAL;
475
476         return sd_bus_track_count_name(track, sender);
477 }
478
479 _public_ int sd_bus_track_count_name(sd_bus_track *track, const char *name) {
480         struct track_item *i;
481
482         assert_return(service_name_is_valid(name), -EINVAL);
483
484         if (!track) /* Let's consider a NULL object equivalent to an empty object */
485                 return 0;
486
487         i = hashmap_get(track->names, name);
488         if (!i)
489                 return 0;
490
491         return i->n_ref;
492 }