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