]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
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" | |
3ffd4af2 | 8 | #include "bus-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 LP |
42 | static struct track_item* track_item_free(struct track_item *i) { |
43 | ||
44 | if (!i) | |
45 | return NULL; | |
46 | ||
47 | sd_bus_slot_unref(i->slot); | |
48 | free(i->name); | |
6b430fdb | 49 | return mfree(i); |
ae1a2efa LP |
50 | } |
51 | ||
52 | DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item*, track_item_free); | |
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? */ | |
72 | if (hashmap_size(track->names) > 0) | |
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) { |
8f8f05a9 LP |
167 | sd_bus_track *track = userdata; |
168 | const char *name, *old, *new; | |
169 | int r; | |
170 | ||
8f8f05a9 LP |
171 | assert(message); |
172 | assert(track); | |
173 | ||
174 | r = sd_bus_message_read(message, "sss", &name, &old, &new); | |
175 | if (r < 0) | |
176 | return 0; | |
177 | ||
ae1a2efa | 178 | bus_track_remove_name_fully(track, name); |
8f8f05a9 LP |
179 | return 0; |
180 | } | |
181 | ||
182 | _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) { | |
ae1a2efa LP |
183 | _cleanup_(track_item_freep) struct track_item *n = NULL; |
184 | struct track_item *i; | |
8f8f05a9 LP |
185 | const char *match; |
186 | int r; | |
187 | ||
188 | assert_return(track, -EINVAL); | |
189 | assert_return(service_name_is_valid(name), -EINVAL); | |
190 | ||
ae1a2efa LP |
191 | i = hashmap_get(track->names, name); |
192 | if (i) { | |
193 | if (track->recursive) { | |
194 | unsigned k = track->n_ref + 1; | |
195 | ||
196 | if (k < track->n_ref) /* Check for overflow */ | |
197 | return -EOVERFLOW; | |
198 | ||
199 | track->n_ref = k; | |
200 | } | |
201 | ||
202 | bus_track_remove_from_queue(track); | |
203 | return 0; | |
204 | } | |
205 | ||
f1bcdd05 | 206 | r = hashmap_ensure_allocated(&track->names, &track_item_hash_ops); |
8f8f05a9 LP |
207 | if (r < 0) |
208 | return r; | |
209 | ||
ae1a2efa | 210 | n = new0(struct track_item, 1); |
8f8f05a9 LP |
211 | if (!n) |
212 | return -ENOMEM; | |
ae1a2efa LP |
213 | n->name = strdup(name); |
214 | if (!n->name) | |
215 | return -ENOMEM; | |
8f8f05a9 | 216 | |
19befb2d | 217 | /* First, subscribe to this name */ |
ae1a2efa | 218 | match = MATCH_FOR_NAME(name); |
8b6e65ac LP |
219 | |
220 | bus_track_remove_from_queue(track); /* don't dispatch this while we work in it */ | |
221 | ||
75152a4d | 222 | r = sd_bus_add_match_async(track->bus, &n->slot, match, on_name_owner_changed, NULL, track); |
8b6e65ac LP |
223 | if (r < 0) { |
224 | bus_track_add_to_queue(track); | |
19befb2d | 225 | return r; |
8b6e65ac | 226 | } |
19befb2d | 227 | |
ae1a2efa | 228 | r = hashmap_put(track->names, n->name, n); |
8b6e65ac LP |
229 | if (r < 0) { |
230 | bus_track_add_to_queue(track); | |
8f8f05a9 | 231 | return r; |
8b6e65ac | 232 | } |
8f8f05a9 | 233 | |
ae1a2efa | 234 | /* Second, check if it is currently existing, or maybe doesn't, or maybe disappeared already. */ |
8b6e65ac | 235 | track->n_adding++; /* again, make sure this isn't dispatch while we are working in it */ |
ae1a2efa | 236 | r = sd_bus_get_name_creds(track->bus, name, 0, NULL); |
8b6e65ac | 237 | track->n_adding--; |
8f8f05a9 | 238 | if (r < 0) { |
ae1a2efa | 239 | hashmap_remove(track->names, name); |
8b6e65ac | 240 | bus_track_add_to_queue(track); |
8f8f05a9 LP |
241 | return r; |
242 | } | |
243 | ||
ae1a2efa | 244 | n->n_ref = 1; |
8f8f05a9 LP |
245 | n = NULL; |
246 | ||
247 | bus_track_remove_from_queue(track); | |
248 | track->modified = true; | |
249 | ||
250 | return 1; | |
251 | } | |
252 | ||
253 | _public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) { | |
ae1a2efa | 254 | struct track_item *i; |
8f8f05a9 LP |
255 | |
256 | assert_return(name, -EINVAL); | |
257 | ||
ae1a2efa | 258 | if (!track) /* Treat a NULL track object as an empty track object */ |
8f8f05a9 LP |
259 | return 0; |
260 | ||
ae1a2efa LP |
261 | if (!track->recursive) |
262 | return bus_track_remove_name_fully(track, name); | |
8f8f05a9 | 263 | |
ae1a2efa LP |
264 | i = hashmap_get(track->names, name); |
265 | if (!i) | |
266 | return -EUNATCH; | |
267 | if (i->n_ref <= 0) | |
268 | return -EUNATCH; | |
8f8f05a9 | 269 | |
ae1a2efa LP |
270 | i->n_ref--; |
271 | ||
272 | if (i->n_ref <= 0) | |
273 | return bus_track_remove_name_fully(track, name); | |
8f8f05a9 LP |
274 | |
275 | return 1; | |
276 | } | |
277 | ||
278 | _public_ unsigned sd_bus_track_count(sd_bus_track *track) { | |
ae1a2efa LP |
279 | |
280 | if (!track) /* Let's consider a NULL object equivalent to an empty object */ | |
8f8f05a9 LP |
281 | return 0; |
282 | ||
ae1a2efa LP |
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 | ||
19befb2d | 287 | return hashmap_size(track->names); |
8f8f05a9 LP |
288 | } |
289 | ||
290 | _public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) { | |
8f8f05a9 LP |
291 | assert_return(name, NULL); |
292 | ||
ae1a2efa LP |
293 | if (!track) /* Let's consider a NULL object equivalent to an empty object */ |
294 | return NULL; | |
295 | ||
19befb2d | 296 | return hashmap_get(track->names, (void*) name) ? name : NULL; |
8f8f05a9 LP |
297 | } |
298 | ||
299 | _public_ const char* sd_bus_track_first(sd_bus_track *track) { | |
19befb2d LP |
300 | const char *n = NULL; |
301 | ||
8f8f05a9 LP |
302 | if (!track) |
303 | return NULL; | |
304 | ||
305 | track->modified = false; | |
4dd6c572 | 306 | track->iterator = ITERATOR_FIRST; |
8f8f05a9 | 307 | |
8927b1da | 308 | hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n); |
19befb2d | 309 | return n; |
8f8f05a9 LP |
310 | } |
311 | ||
312 | _public_ const char* sd_bus_track_next(sd_bus_track *track) { | |
19befb2d LP |
313 | const char *n = NULL; |
314 | ||
8f8f05a9 LP |
315 | if (!track) |
316 | return NULL; | |
317 | ||
318 | if (track->modified) | |
319 | return NULL; | |
320 | ||
8927b1da | 321 | hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n); |
19befb2d | 322 | return n; |
8f8f05a9 LP |
323 | } |
324 | ||
325 | _public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) { | |
326 | const char *sender; | |
327 | ||
328 | assert_return(track, -EINVAL); | |
329 | assert_return(m, -EINVAL); | |
330 | ||
ae1a2efa LP |
331 | if (sd_bus_message_get_bus(m) != track->bus) |
332 | return -EINVAL; | |
333 | ||
8f8f05a9 LP |
334 | sender = sd_bus_message_get_sender(m); |
335 | if (!sender) | |
336 | return -EINVAL; | |
337 | ||
338 | return sd_bus_track_add_name(track, sender); | |
339 | } | |
340 | ||
341 | _public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) { | |
342 | const char *sender; | |
343 | ||
8f8f05a9 LP |
344 | assert_return(m, -EINVAL); |
345 | ||
ae1a2efa LP |
346 | if (!track) /* Treat a NULL track object as an empty track object */ |
347 | return 0; | |
348 | ||
349 | if (sd_bus_message_get_bus(m) != track->bus) | |
350 | return -EINVAL; | |
351 | ||
8f8f05a9 LP |
352 | sender = sd_bus_message_get_sender(m); |
353 | if (!sender) | |
354 | return -EINVAL; | |
355 | ||
356 | return sd_bus_track_remove_name(track, sender); | |
357 | } | |
358 | ||
359 | _public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) { | |
360 | assert_return(track, NULL); | |
361 | ||
362 | return track->bus; | |
363 | } | |
364 | ||
365 | void bus_track_dispatch(sd_bus_track *track) { | |
366 | int r; | |
367 | ||
368 | assert(track); | |
8f8f05a9 LP |
369 | assert(track->handler); |
370 | ||
371 | bus_track_remove_from_queue(track); | |
372 | ||
373 | sd_bus_track_ref(track); | |
374 | ||
375 | r = track->handler(track, track->userdata); | |
376 | if (r < 0) | |
da927ba9 | 377 | log_debug_errno(r, "Failed to process track handler: %m"); |
8f8f05a9 LP |
378 | else if (r == 0) |
379 | bus_track_add_to_queue(track); | |
380 | ||
381 | sd_bus_track_unref(track); | |
382 | } | |
04552566 | 383 | |
232f3677 | 384 | void bus_track_close(sd_bus_track *track) { |
232f3677 LP |
385 | 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 | if (!track->in_list) | |
391 | return; /* We already closed this one, don't close it again. */ | |
392 | ||
393 | /* Remember that this one is closed now */ | |
394 | LIST_REMOVE(tracks, track->bus->tracks, track); | |
395 | track->in_list = false; | |
396 | ||
397 | /* If there's no name in this one anyway, we don't have to dispatch */ | |
398 | if (hashmap_isempty(track->names)) | |
399 | return; | |
400 | ||
401 | /* Let's flush out all names */ | |
f1bcdd05 | 402 | hashmap_clear(track->names); |
232f3677 LP |
403 | |
404 | /* Invoke handler */ | |
405 | if (track->handler) | |
406 | bus_track_dispatch(track); | |
407 | } | |
408 | ||
04552566 LP |
409 | _public_ void *sd_bus_track_get_userdata(sd_bus_track *track) { |
410 | assert_return(track, NULL); | |
411 | ||
412 | return track->userdata; | |
413 | } | |
414 | ||
415 | _public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) { | |
416 | void *ret; | |
417 | ||
418 | assert_return(track, NULL); | |
419 | ||
420 | ret = track->userdata; | |
421 | track->userdata = userdata; | |
422 | ||
423 | return ret; | |
424 | } | |
ae1a2efa | 425 | |
66223497 LP |
426 | _public_ int sd_bus_track_set_destroy_callback(sd_bus_track *track, sd_bus_destroy_t callback) { |
427 | assert_return(track, -EINVAL); | |
428 | ||
429 | track->destroy_callback = callback; | |
430 | return 0; | |
431 | } | |
432 | ||
433 | _public_ int sd_bus_track_get_destroy_callback(sd_bus_track *track, sd_bus_destroy_t *ret) { | |
434 | assert_return(track, -EINVAL); | |
435 | ||
436 | if (ret) | |
437 | *ret = track->destroy_callback; | |
438 | ||
439 | return !!track->destroy_callback; | |
440 | } | |
441 | ||
ae1a2efa LP |
442 | _public_ int sd_bus_track_set_recursive(sd_bus_track *track, int b) { |
443 | assert_return(track, -EINVAL); | |
444 | ||
445 | if (track->recursive == !!b) | |
446 | return 0; | |
447 | ||
448 | if (!hashmap_isempty(track->names)) | |
449 | return -EBUSY; | |
450 | ||
451 | track->recursive = b; | |
452 | return 0; | |
453 | } | |
454 | ||
455 | _public_ int sd_bus_track_get_recursive(sd_bus_track *track) { | |
456 | assert_return(track, -EINVAL); | |
457 | ||
458 | return track->recursive; | |
459 | } | |
460 | ||
461 | _public_ int sd_bus_track_count_sender(sd_bus_track *track, sd_bus_message *m) { | |
462 | const char *sender; | |
463 | ||
464 | assert_return(m, -EINVAL); | |
465 | ||
466 | if (!track) /* Let's consider a NULL object equivalent to an empty object */ | |
467 | return 0; | |
468 | ||
469 | if (sd_bus_message_get_bus(m) != track->bus) | |
470 | return -EINVAL; | |
471 | ||
472 | sender = sd_bus_message_get_sender(m); | |
473 | if (!sender) | |
474 | return -EINVAL; | |
475 | ||
476 | return sd_bus_track_count_name(track, sender); | |
477 | } | |
478 | ||
479 | _public_ int sd_bus_track_count_name(sd_bus_track *track, const char *name) { | |
480 | struct track_item *i; | |
481 | ||
482 | assert_return(service_name_is_valid(name), -EINVAL); | |
483 | ||
484 | if (!track) /* Let's consider a NULL object equivalent to an empty object */ | |
485 | return 0; | |
486 | ||
487 | i = hashmap_get(track->names, name); | |
488 | if (!i) | |
489 | return 0; | |
490 | ||
491 | return i->n_ref; | |
492 | } |