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