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