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