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