1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include "sd-bus.h"
4 
5 #include "alloc-util.h"
6 #include "bus-internal.h"
7 #include "bus-track.h"
8 #include "string-util.h"
9 
10 struct track_item {
11         unsigned n_ref;
12         char *name;
13         sd_bus_slot *slot;
14 };
15 
16 struct sd_bus_track {
17         unsigned n_ref;
18         unsigned n_adding; /* are we in the process of adding a new name? */
19         sd_bus *bus;
20         sd_bus_track_handler_t handler;
21         void *userdata;
22         Hashmap *names;
23         LIST_FIELDS(sd_bus_track, queue);
24         Iterator iterator;
25         bool in_list:1;    /* In bus->tracks? */
26         bool in_queue:1;   /* In bus->track_queue? */
27         bool modified:1;
28         bool recursive:1;
29         sd_bus_destroy_t destroy_callback;
30 
31         LIST_FIELDS(sd_bus_track, tracks);
32 };
33 
34 #define MATCH_FOR_NAME(name)                            \
35         strjoina("type='signal',"                       \
36                  "sender='org.freedesktop.DBus',"       \
37                  "path='/org/freedesktop/DBus',"        \
38                  "interface='org.freedesktop.DBus',"    \
39                  "member='NameOwnerChanged',"           \
40                  "arg0='", name, "'")
41 
track_item_free(struct track_item * i)42 static struct track_item* track_item_free(struct track_item *i) {
43         if (!i)
44                 return NULL;
45 
46         sd_bus_slot_unref(i->slot);
47         free(i->name);
48         return mfree(i);
49 }
50 
51 DEFINE_PRIVATE_TRIVIAL_UNREF_FUNC(struct track_item, track_item, track_item_free);
52 DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item*, track_item_unref);
53 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(track_item_hash_ops, char, string_hash_func, string_compare_func,
54                                               struct track_item, track_item_free);
55 
bus_track_add_to_queue(sd_bus_track * track)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 
bus_track_remove_from_queue(sd_bus_track * track)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 
bus_track_remove_name_fully(sd_bus_track * track,const char * name)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 
sd_bus_track_new(sd_bus * bus,sd_bus_track ** track,sd_bus_track_handler_t handler,void * userdata)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 
track_free(sd_bus_track * track)148 static sd_bus_track *track_free(sd_bus_track *track) {
149         assert(track);
150 
151         if (track->in_list)
152                 LIST_REMOVE(tracks, track->bus->tracks, track);
153 
154         bus_track_remove_from_queue(track);
155         track->names = hashmap_free(track->names);
156         track->bus = sd_bus_unref(track->bus);
157 
158         if (track->destroy_callback)
159                 track->destroy_callback(track->userdata);
160 
161         return mfree(track);
162 }
163 
164 DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_bus_track, sd_bus_track, track_free);
165 
on_name_owner_changed(sd_bus_message * message,void * userdata,sd_bus_error * error)166 static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
167         sd_bus_track *track = userdata;
168         const char *name;
169         int r;
170 
171         assert(message);
172         assert(track);
173 
174         r = sd_bus_message_read(message, "sss", &name, NULL, NULL);
175         if (r < 0)
176                 return 0;
177 
178         bus_track_remove_name_fully(track, name);
179         return 0;
180 }
181 
sd_bus_track_add_name(sd_bus_track * track,const char * name)182 _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) {
183         _cleanup_(track_item_unrefp) struct track_item *n = NULL;
184         struct track_item *i;
185         const char *match;
186         int r;
187 
188         assert_return(track, -EINVAL);
189         assert_return(service_name_is_valid(name), -EINVAL);
190 
191         i = hashmap_get(track->names, name);
192         if (i) {
193                 if (track->recursive) {
194                         assert(i->n_ref > 0);
195 
196                         /* Manual overflow check (instead of a DEFINE_TRIVIAL_REF_FUNC() helper or so), so
197                          * that we can return a proper error, given this is almost always called in a
198                          * directly client controllable way, and thus better should never hit an assertion
199                          * here. */
200                         if (i->n_ref >= UINT_MAX)
201                                 return -EOVERFLOW;
202 
203                         i->n_ref++;
204                 }
205 
206                 bus_track_remove_from_queue(track);
207                 return 0;
208         }
209 
210         r = hashmap_ensure_allocated(&track->names, &track_item_hash_ops);
211         if (r < 0)
212                 return r;
213 
214         n = new(struct track_item, 1);
215         if (!n)
216                 return -ENOMEM;
217 
218         *n = (struct track_item) {
219                 .n_ref = 1,
220         };
221 
222         n->name = strdup(name);
223         if (!n->name)
224                 return -ENOMEM;
225 
226         /* First, subscribe to this name */
227         match = MATCH_FOR_NAME(name);
228 
229         bus_track_remove_from_queue(track); /* don't dispatch this while we work in it */
230 
231         r = sd_bus_add_match_async(track->bus, &n->slot, match, on_name_owner_changed, NULL, track);
232         if (r < 0) {
233                 bus_track_add_to_queue(track);
234                 return r;
235         }
236 
237         r = hashmap_put(track->names, n->name, n);
238         if (r < 0) {
239                 bus_track_add_to_queue(track);
240                 return r;
241         }
242 
243         /* Second, check if it is currently existing, or maybe doesn't, or maybe disappeared already. */
244         track->n_adding++; /* again, make sure this isn't dispatch while we are working in it */
245         r = sd_bus_get_name_creds(track->bus, name, 0, NULL);
246         track->n_adding--;
247         if (r < 0) {
248                 hashmap_remove(track->names, name);
249                 bus_track_add_to_queue(track);
250                 return r;
251         }
252 
253         TAKE_PTR(n);
254 
255         bus_track_remove_from_queue(track);
256         track->modified = true;
257 
258         return 1;
259 }
260 
sd_bus_track_remove_name(sd_bus_track * track,const char * name)261 _public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) {
262         struct track_item *i;
263 
264         assert_return(name, -EINVAL);
265 
266         if (!track) /* Treat a NULL track object as an empty track object */
267                 return 0;
268 
269         i = hashmap_get(track->names, name);
270         if (!i)
271                 return 0;
272 
273         assert(i->n_ref >= 1);
274         if (i->n_ref <= 1)
275                 return bus_track_remove_name_fully(track, name);
276 
277         track_item_unref(i);
278 
279         return 1;
280 }
281 
sd_bus_track_count(sd_bus_track * track)282 _public_ unsigned sd_bus_track_count(sd_bus_track *track) {
283 
284         if (!track) /* Let's consider a NULL object equivalent to an empty object */
285                 return 0;
286 
287         /* This signature really should have returned an int, so that we can propagate errors. But well, ... Also, note
288          * that this returns the number of names being watched, and multiple references to the same name are not
289          * counted. */
290 
291         return hashmap_size(track->names);
292 }
293 
sd_bus_track_contains(sd_bus_track * track,const char * name)294 _public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) {
295         assert_return(name, NULL);
296 
297         if (!track) /* Let's consider a NULL object equivalent to an empty object */
298                 return NULL;
299 
300         return hashmap_contains(track->names, name) ? name : NULL;
301 }
302 
sd_bus_track_first(sd_bus_track * track)303 _public_ const char* sd_bus_track_first(sd_bus_track *track) {
304         const char *n = NULL;
305 
306         if (!track)
307                 return NULL;
308 
309         track->modified = false;
310         track->iterator = ITERATOR_FIRST;
311 
312         (void) hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
313         return n;
314 }
315 
sd_bus_track_next(sd_bus_track * track)316 _public_ const char* sd_bus_track_next(sd_bus_track *track) {
317         const char *n = NULL;
318 
319         if (!track)
320                 return NULL;
321 
322         if (track->modified)
323                 return NULL;
324 
325         (void) hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
326         return n;
327 }
328 
sd_bus_track_add_sender(sd_bus_track * track,sd_bus_message * m)329 _public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) {
330         const char *sender;
331 
332         assert_return(track, -EINVAL);
333         assert_return(m, -EINVAL);
334 
335         if (sd_bus_message_get_bus(m) != track->bus)
336                 return -EINVAL;
337 
338         sender = sd_bus_message_get_sender(m);
339         if (!sender)
340                 return -EINVAL;
341 
342         return sd_bus_track_add_name(track, sender);
343 }
344 
sd_bus_track_remove_sender(sd_bus_track * track,sd_bus_message * m)345 _public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) {
346         const char *sender;
347 
348         assert_return(m, -EINVAL);
349 
350         if (!track) /* Treat a NULL track object as an empty track object */
351                 return 0;
352 
353         if (sd_bus_message_get_bus(m) != track->bus)
354                 return -EINVAL;
355 
356         sender = sd_bus_message_get_sender(m);
357         if (!sender)
358                 return -EINVAL;
359 
360         return sd_bus_track_remove_name(track, sender);
361 }
362 
sd_bus_track_get_bus(sd_bus_track * track)363 _public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) {
364         assert_return(track, NULL);
365 
366         return track->bus;
367 }
368 
bus_track_dispatch(sd_bus_track * track)369 void bus_track_dispatch(sd_bus_track *track) {
370         int r;
371 
372         assert(track);
373         assert(track->handler);
374 
375         bus_track_remove_from_queue(track);
376 
377         sd_bus_track_ref(track);
378 
379         r = track->handler(track, track->userdata);
380         if (r < 0)
381                 log_debug_errno(r, "Failed to process track handler: %m");
382         else if (r == 0)
383                 bus_track_add_to_queue(track);
384 
385         sd_bus_track_unref(track);
386 }
387 
bus_track_close(sd_bus_track * track)388 void bus_track_close(sd_bus_track *track) {
389         assert(track);
390 
391         /* Called whenever our bus connected is closed. If so, and our track object is non-empty, dispatch it
392          * immediately, as we are closing now, but first flush out all names. */
393 
394         if (!track->in_list)
395                 return; /* We already closed this one, don't close it again. */
396 
397         /* Remember that this one is closed now */
398         LIST_REMOVE(tracks, track->bus->tracks, track);
399         track->in_list = false;
400 
401         /* If there's no name in this one anyway, we don't have to dispatch */
402         if (hashmap_isempty(track->names))
403                 return;
404 
405         /* Let's flush out all names */
406         hashmap_clear(track->names);
407 
408         /* Invoke handler */
409         if (track->handler)
410                 bus_track_dispatch(track);
411 }
412 
sd_bus_track_get_userdata(sd_bus_track * track)413 _public_ void *sd_bus_track_get_userdata(sd_bus_track *track) {
414         assert_return(track, NULL);
415 
416         return track->userdata;
417 }
418 
sd_bus_track_set_userdata(sd_bus_track * track,void * userdata)419 _public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) {
420         void *ret;
421 
422         assert_return(track, NULL);
423 
424         ret = track->userdata;
425         track->userdata = userdata;
426 
427         return ret;
428 }
429 
sd_bus_track_set_destroy_callback(sd_bus_track * track,sd_bus_destroy_t callback)430 _public_ int sd_bus_track_set_destroy_callback(sd_bus_track *track, sd_bus_destroy_t callback) {
431         assert_return(track, -EINVAL);
432 
433         track->destroy_callback = callback;
434         return 0;
435 }
436 
sd_bus_track_get_destroy_callback(sd_bus_track * track,sd_bus_destroy_t * ret)437 _public_ int sd_bus_track_get_destroy_callback(sd_bus_track *track, sd_bus_destroy_t *ret) {
438         assert_return(track, -EINVAL);
439 
440         if (ret)
441                 *ret = track->destroy_callback;
442 
443         return !!track->destroy_callback;
444 }
445 
sd_bus_track_set_recursive(sd_bus_track * track,int b)446 _public_ int sd_bus_track_set_recursive(sd_bus_track *track, int b) {
447         assert_return(track, -EINVAL);
448 
449         if (track->recursive == !!b)
450                 return 0;
451 
452         if (!hashmap_isempty(track->names))
453                 return -EBUSY;
454 
455         track->recursive = b;
456         return 0;
457 }
458 
sd_bus_track_get_recursive(sd_bus_track * track)459 _public_ int sd_bus_track_get_recursive(sd_bus_track *track) {
460         assert_return(track, -EINVAL);
461 
462         return track->recursive;
463 }
464 
sd_bus_track_count_sender(sd_bus_track * track,sd_bus_message * m)465 _public_ int sd_bus_track_count_sender(sd_bus_track *track, sd_bus_message *m) {
466         const char *sender;
467 
468         assert_return(m, -EINVAL);
469 
470         if (!track) /* Let's consider a NULL object equivalent to an empty object */
471                 return 0;
472 
473         if (sd_bus_message_get_bus(m) != track->bus)
474                 return -EINVAL;
475 
476         sender = sd_bus_message_get_sender(m);
477         if (!sender)
478                 return -EINVAL;
479 
480         return sd_bus_track_count_name(track, sender);
481 }
482 
sd_bus_track_count_name(sd_bus_track * track,const char * name)483 _public_ int sd_bus_track_count_name(sd_bus_track *track, const char *name) {
484         struct track_item *i;
485 
486         assert_return(service_name_is_valid(name), -EINVAL);
487 
488         if (!track) /* Let's consider a NULL object equivalent to an empty object */
489                 return 0;
490 
491         i = hashmap_get(track->names, name);
492         if (!i)
493                 return 0;
494 
495         return i->n_ref;
496 }
497