]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
8f8f05a9 LP |
2 | /*** |
3 | This file is part of systemd. | |
4 | ||
5 | Copyright 2013 Lennart Poettering | |
8f8f05a9 LP |
6 | ***/ |
7 | ||
8 | #include "sd-bus.h" | |
3ffd4af2 | 9 | |
b5efdb8a | 10 | #include "alloc-util.h" |
8f8f05a9 LP |
11 | #include "bus-internal.h" |
12 | #include "bus-track.h" | |
3ffd4af2 | 13 | #include "bus-util.h" |
8f8f05a9 | 14 | |
ae1a2efa LP |
15 | struct track_item { |
16 | unsigned n_ref; | |
17 | char *name; | |
18 | sd_bus_slot *slot; | |
19 | }; | |
20 | ||
8f8f05a9 LP |
21 | struct sd_bus_track { |
22 | unsigned n_ref; | |
8b6e65ac | 23 | unsigned n_adding; /* are we in the process of adding a new name? */ |
8f8f05a9 LP |
24 | sd_bus *bus; |
25 | sd_bus_track_handler_t handler; | |
26 | void *userdata; | |
19befb2d | 27 | Hashmap *names; |
8f8f05a9 LP |
28 | LIST_FIELDS(sd_bus_track, queue); |
29 | Iterator iterator; | |
232f3677 LP |
30 | bool in_list:1; /* In bus->tracks? */ |
31 | bool in_queue:1; /* In bus->track_queue? */ | |
ae1a2efa LP |
32 | bool modified:1; |
33 | bool recursive:1; | |
232f3677 LP |
34 | |
35 | LIST_FIELDS(sd_bus_track, tracks); | |
8f8f05a9 LP |
36 | }; |
37 | ||
f9d5fcce LP |
38 | #define MATCH_FOR_NAME(name) \ |
39 | strjoina("type='signal'," \ | |
40 | "sender='org.freedesktop.DBus'," \ | |
41 | "path='/org/freedesktop/DBus'," \ | |
42 | "interface='org.freedesktop.DBus'," \ | |
43 | "member='NameOwnerChanged'," \ | |
44 | "arg0='", name, "'") | |
8f8f05a9 | 45 | |
ae1a2efa LP |
46 | static struct track_item* track_item_free(struct track_item *i) { |
47 | ||
48 | if (!i) | |
49 | return NULL; | |
50 | ||
51 | sd_bus_slot_unref(i->slot); | |
52 | free(i->name); | |
6b430fdb | 53 | return mfree(i); |
ae1a2efa LP |
54 | } |
55 | ||
56 | DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item*, track_item_free); | |
57 | ||
8f8f05a9 LP |
58 | static void bus_track_add_to_queue(sd_bus_track *track) { |
59 | assert(track); | |
60 | ||
8b6e65ac LP |
61 | /* Adds the bus track object to the queue of objects we should dispatch next, subject to a number of |
62 | * conditions. */ | |
63 | ||
64 | /* Already in the queue? */ | |
8f8f05a9 LP |
65 | if (track->in_queue) |
66 | return; | |
67 | ||
8b6e65ac LP |
68 | /* if we are currently in the process of adding a new name, then let's not enqueue this just yet, let's wait |
69 | * until the addition is complete. */ | |
70 | if (track->n_adding > 0) | |
71 | return; | |
72 | ||
73 | /* still referenced? */ | |
74 | if (hashmap_size(track->names) > 0) | |
75 | return; | |
76 | ||
77 | /* Nothing to call? */ | |
8f8f05a9 LP |
78 | if (!track->handler) |
79 | return; | |
80 | ||
232f3677 LP |
81 | /* Already closed? */ |
82 | if (!track->in_list) | |
83 | return; | |
84 | ||
8f8f05a9 LP |
85 | LIST_PREPEND(queue, track->bus->track_queue, track); |
86 | track->in_queue = true; | |
87 | } | |
88 | ||
89 | static void bus_track_remove_from_queue(sd_bus_track *track) { | |
90 | assert(track); | |
91 | ||
92 | if (!track->in_queue) | |
93 | return; | |
94 | ||
95 | LIST_REMOVE(queue, track->bus->track_queue, track); | |
96 | track->in_queue = false; | |
97 | } | |
98 | ||
ae1a2efa LP |
99 | static int bus_track_remove_name_fully(sd_bus_track *track, const char *name) { |
100 | struct track_item *i; | |
101 | ||
102 | assert(track); | |
103 | assert(name); | |
104 | ||
105 | i = hashmap_remove(track->names, name); | |
106 | if (!i) | |
107 | return 0; | |
108 | ||
109 | track_item_free(i); | |
110 | ||
8b6e65ac | 111 | bus_track_add_to_queue(track); |
ae1a2efa LP |
112 | |
113 | track->modified = true; | |
114 | return 1; | |
115 | } | |
116 | ||
8f8f05a9 LP |
117 | _public_ int sd_bus_track_new( |
118 | sd_bus *bus, | |
119 | sd_bus_track **track, | |
120 | sd_bus_track_handler_t handler, | |
121 | void *userdata) { | |
122 | ||
123 | sd_bus_track *t; | |
124 | ||
125 | assert_return(bus, -EINVAL); | |
45b1f410 | 126 | assert_return(bus = bus_resolve(bus), -ENOPKG); |
8f8f05a9 LP |
127 | assert_return(track, -EINVAL); |
128 | ||
33c62dcb LP |
129 | if (!bus->bus_client) |
130 | return -EINVAL; | |
131 | ||
8f8f05a9 LP |
132 | t = new0(sd_bus_track, 1); |
133 | if (!t) | |
134 | return -ENOMEM; | |
135 | ||
136 | t->n_ref = 1; | |
137 | t->handler = handler; | |
138 | t->userdata = userdata; | |
139 | t->bus = sd_bus_ref(bus); | |
140 | ||
232f3677 LP |
141 | LIST_PREPEND(tracks, bus->tracks, t); |
142 | t->in_list = true; | |
143 | ||
8f8f05a9 LP |
144 | bus_track_add_to_queue(t); |
145 | ||
146 | *track = t; | |
147 | return 0; | |
148 | } | |
149 | ||
150 | _public_ sd_bus_track* sd_bus_track_ref(sd_bus_track *track) { | |
4afd3348 LP |
151 | |
152 | if (!track) | |
153 | return NULL; | |
8f8f05a9 LP |
154 | |
155 | assert(track->n_ref > 0); | |
156 | ||
157 | track->n_ref++; | |
158 | ||
159 | return track; | |
160 | } | |
161 | ||
162 | _public_ sd_bus_track* sd_bus_track_unref(sd_bus_track *track) { | |
8f8f05a9 LP |
163 | if (!track) |
164 | return NULL; | |
165 | ||
166 | assert(track->n_ref > 0); | |
167 | ||
168 | if (track->n_ref > 1) { | |
313cefa1 | 169 | track->n_ref--; |
8f8f05a9 LP |
170 | return NULL; |
171 | } | |
172 | ||
232f3677 LP |
173 | if (track->in_list) |
174 | LIST_REMOVE(tracks, track->bus->tracks, track); | |
175 | ||
8f8f05a9 | 176 | bus_track_remove_from_queue(track); |
224b0e7a | 177 | hashmap_free_with_destructor(track->names, track_item_free); |
8f8f05a9 | 178 | sd_bus_unref(track->bus); |
6b430fdb | 179 | return mfree(track); |
8f8f05a9 LP |
180 | } |
181 | ||
19070062 | 182 | static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { |
8f8f05a9 LP |
183 | sd_bus_track *track = userdata; |
184 | const char *name, *old, *new; | |
185 | int r; | |
186 | ||
8f8f05a9 LP |
187 | assert(message); |
188 | assert(track); | |
189 | ||
190 | r = sd_bus_message_read(message, "sss", &name, &old, &new); | |
191 | if (r < 0) | |
192 | return 0; | |
193 | ||
ae1a2efa | 194 | bus_track_remove_name_fully(track, name); |
8f8f05a9 LP |
195 | return 0; |
196 | } | |
197 | ||
198 | _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) { | |
ae1a2efa LP |
199 | _cleanup_(track_item_freep) struct track_item *n = NULL; |
200 | struct track_item *i; | |
8f8f05a9 LP |
201 | const char *match; |
202 | int r; | |
203 | ||
204 | assert_return(track, -EINVAL); | |
205 | assert_return(service_name_is_valid(name), -EINVAL); | |
206 | ||
ae1a2efa LP |
207 | i = hashmap_get(track->names, name); |
208 | if (i) { | |
209 | if (track->recursive) { | |
210 | unsigned k = track->n_ref + 1; | |
211 | ||
212 | if (k < track->n_ref) /* Check for overflow */ | |
213 | return -EOVERFLOW; | |
214 | ||
215 | track->n_ref = k; | |
216 | } | |
217 | ||
218 | bus_track_remove_from_queue(track); | |
219 | return 0; | |
220 | } | |
221 | ||
d5099efc | 222 | r = hashmap_ensure_allocated(&track->names, &string_hash_ops); |
8f8f05a9 LP |
223 | if (r < 0) |
224 | return r; | |
225 | ||
ae1a2efa | 226 | n = new0(struct track_item, 1); |
8f8f05a9 LP |
227 | if (!n) |
228 | return -ENOMEM; | |
ae1a2efa LP |
229 | n->name = strdup(name); |
230 | if (!n->name) | |
231 | return -ENOMEM; | |
8f8f05a9 | 232 | |
19befb2d | 233 | /* First, subscribe to this name */ |
ae1a2efa | 234 | match = MATCH_FOR_NAME(name); |
8b6e65ac LP |
235 | |
236 | bus_track_remove_from_queue(track); /* don't dispatch this while we work in it */ | |
237 | ||
75152a4d | 238 | r = sd_bus_add_match_async(track->bus, &n->slot, match, on_name_owner_changed, NULL, track); |
8b6e65ac LP |
239 | if (r < 0) { |
240 | bus_track_add_to_queue(track); | |
19befb2d | 241 | return r; |
8b6e65ac | 242 | } |
19befb2d | 243 | |
ae1a2efa | 244 | r = hashmap_put(track->names, n->name, n); |
8b6e65ac LP |
245 | if (r < 0) { |
246 | bus_track_add_to_queue(track); | |
8f8f05a9 | 247 | return r; |
8b6e65ac | 248 | } |
8f8f05a9 | 249 | |
ae1a2efa | 250 | /* Second, check if it is currently existing, or maybe doesn't, or maybe disappeared already. */ |
8b6e65ac | 251 | track->n_adding++; /* again, make sure this isn't dispatch while we are working in it */ |
ae1a2efa | 252 | r = sd_bus_get_name_creds(track->bus, name, 0, NULL); |
8b6e65ac | 253 | track->n_adding--; |
8f8f05a9 | 254 | if (r < 0) { |
ae1a2efa | 255 | hashmap_remove(track->names, name); |
8b6e65ac | 256 | bus_track_add_to_queue(track); |
8f8f05a9 LP |
257 | return r; |
258 | } | |
259 | ||
ae1a2efa | 260 | n->n_ref = 1; |
8f8f05a9 LP |
261 | n = NULL; |
262 | ||
263 | bus_track_remove_from_queue(track); | |
264 | track->modified = true; | |
265 | ||
266 | return 1; | |
267 | } | |
268 | ||
269 | _public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) { | |
ae1a2efa | 270 | struct track_item *i; |
8f8f05a9 LP |
271 | |
272 | assert_return(name, -EINVAL); | |
273 | ||
ae1a2efa | 274 | if (!track) /* Treat a NULL track object as an empty track object */ |
8f8f05a9 LP |
275 | return 0; |
276 | ||
ae1a2efa LP |
277 | if (!track->recursive) |
278 | return bus_track_remove_name_fully(track, name); | |
8f8f05a9 | 279 | |
ae1a2efa LP |
280 | i = hashmap_get(track->names, name); |
281 | if (!i) | |
282 | return -EUNATCH; | |
283 | if (i->n_ref <= 0) | |
284 | return -EUNATCH; | |
8f8f05a9 | 285 | |
ae1a2efa LP |
286 | i->n_ref--; |
287 | ||
288 | if (i->n_ref <= 0) | |
289 | return bus_track_remove_name_fully(track, name); | |
8f8f05a9 LP |
290 | |
291 | return 1; | |
292 | } | |
293 | ||
294 | _public_ unsigned sd_bus_track_count(sd_bus_track *track) { | |
ae1a2efa LP |
295 | |
296 | if (!track) /* Let's consider a NULL object equivalent to an empty object */ | |
8f8f05a9 LP |
297 | return 0; |
298 | ||
ae1a2efa LP |
299 | /* This signature really should have returned an int, so that we can propagate errors. But well, ... Also, note |
300 | * that this returns the number of names being watched, and multiple references to the same name are not | |
301 | * counted. */ | |
302 | ||
19befb2d | 303 | return hashmap_size(track->names); |
8f8f05a9 LP |
304 | } |
305 | ||
306 | _public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) { | |
8f8f05a9 LP |
307 | assert_return(name, NULL); |
308 | ||
ae1a2efa LP |
309 | if (!track) /* Let's consider a NULL object equivalent to an empty object */ |
310 | return NULL; | |
311 | ||
19befb2d | 312 | return hashmap_get(track->names, (void*) name) ? name : NULL; |
8f8f05a9 LP |
313 | } |
314 | ||
315 | _public_ const char* sd_bus_track_first(sd_bus_track *track) { | |
19befb2d LP |
316 | const char *n = NULL; |
317 | ||
8f8f05a9 LP |
318 | if (!track) |
319 | return NULL; | |
320 | ||
321 | track->modified = false; | |
4dd6c572 | 322 | track->iterator = ITERATOR_FIRST; |
8f8f05a9 | 323 | |
8927b1da | 324 | hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n); |
19befb2d | 325 | return n; |
8f8f05a9 LP |
326 | } |
327 | ||
328 | _public_ const char* sd_bus_track_next(sd_bus_track *track) { | |
19befb2d LP |
329 | const char *n = NULL; |
330 | ||
8f8f05a9 LP |
331 | if (!track) |
332 | return NULL; | |
333 | ||
334 | if (track->modified) | |
335 | return NULL; | |
336 | ||
8927b1da | 337 | hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n); |
19befb2d | 338 | return n; |
8f8f05a9 LP |
339 | } |
340 | ||
341 | _public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) { | |
342 | const char *sender; | |
343 | ||
344 | assert_return(track, -EINVAL); | |
345 | assert_return(m, -EINVAL); | |
346 | ||
ae1a2efa LP |
347 | if (sd_bus_message_get_bus(m) != track->bus) |
348 | return -EINVAL; | |
349 | ||
8f8f05a9 LP |
350 | sender = sd_bus_message_get_sender(m); |
351 | if (!sender) | |
352 | return -EINVAL; | |
353 | ||
354 | return sd_bus_track_add_name(track, sender); | |
355 | } | |
356 | ||
357 | _public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) { | |
358 | const char *sender; | |
359 | ||
8f8f05a9 LP |
360 | assert_return(m, -EINVAL); |
361 | ||
ae1a2efa LP |
362 | if (!track) /* Treat a NULL track object as an empty track object */ |
363 | return 0; | |
364 | ||
365 | if (sd_bus_message_get_bus(m) != track->bus) | |
366 | return -EINVAL; | |
367 | ||
8f8f05a9 LP |
368 | sender = sd_bus_message_get_sender(m); |
369 | if (!sender) | |
370 | return -EINVAL; | |
371 | ||
372 | return sd_bus_track_remove_name(track, sender); | |
373 | } | |
374 | ||
375 | _public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) { | |
376 | assert_return(track, NULL); | |
377 | ||
378 | return track->bus; | |
379 | } | |
380 | ||
381 | void bus_track_dispatch(sd_bus_track *track) { | |
382 | int r; | |
383 | ||
384 | assert(track); | |
8f8f05a9 LP |
385 | assert(track->handler); |
386 | ||
387 | bus_track_remove_from_queue(track); | |
388 | ||
389 | sd_bus_track_ref(track); | |
390 | ||
391 | r = track->handler(track, track->userdata); | |
392 | if (r < 0) | |
da927ba9 | 393 | log_debug_errno(r, "Failed to process track handler: %m"); |
8f8f05a9 LP |
394 | else if (r == 0) |
395 | bus_track_add_to_queue(track); | |
396 | ||
397 | sd_bus_track_unref(track); | |
398 | } | |
04552566 | 399 | |
232f3677 | 400 | void bus_track_close(sd_bus_track *track) { |
232f3677 LP |
401 | assert(track); |
402 | ||
403 | /* Called whenever our bus connected is closed. If so, and our track object is non-empty, dispatch it | |
404 | * immediately, as we are closing now, but first flush out all names. */ | |
405 | ||
406 | if (!track->in_list) | |
407 | return; /* We already closed this one, don't close it again. */ | |
408 | ||
409 | /* Remember that this one is closed now */ | |
410 | LIST_REMOVE(tracks, track->bus->tracks, track); | |
411 | track->in_list = false; | |
412 | ||
413 | /* If there's no name in this one anyway, we don't have to dispatch */ | |
414 | if (hashmap_isempty(track->names)) | |
415 | return; | |
416 | ||
417 | /* Let's flush out all names */ | |
224b0e7a | 418 | hashmap_clear_with_destructor(track->names, track_item_free); |
232f3677 LP |
419 | |
420 | /* Invoke handler */ | |
421 | if (track->handler) | |
422 | bus_track_dispatch(track); | |
423 | } | |
424 | ||
04552566 LP |
425 | _public_ void *sd_bus_track_get_userdata(sd_bus_track *track) { |
426 | assert_return(track, NULL); | |
427 | ||
428 | return track->userdata; | |
429 | } | |
430 | ||
431 | _public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) { | |
432 | void *ret; | |
433 | ||
434 | assert_return(track, NULL); | |
435 | ||
436 | ret = track->userdata; | |
437 | track->userdata = userdata; | |
438 | ||
439 | return ret; | |
440 | } | |
ae1a2efa LP |
441 | |
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 | } |