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