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