]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd/sd-bus/bus-track.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / libsystemd / sd-bus / bus-track.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
8f8f05a9
LP
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"
3ffd4af2 22
b5efdb8a 23#include "alloc-util.h"
8f8f05a9
LP
24#include "bus-internal.h"
25#include "bus-track.h"
3ffd4af2 26#include "bus-util.h"
8f8f05a9 27
ae1a2efa
LP
28struct track_item {
29 unsigned n_ref;
30 char *name;
31 sd_bus_slot *slot;
32};
33
8f8f05a9
LP
34struct sd_bus_track {
35 unsigned n_ref;
8b6e65ac 36 unsigned n_adding; /* are we in the process of adding a new name? */
8f8f05a9
LP
37 sd_bus *bus;
38 sd_bus_track_handler_t handler;
39 void *userdata;
19befb2d 40 Hashmap *names;
8f8f05a9
LP
41 LIST_FIELDS(sd_bus_track, queue);
42 Iterator iterator;
232f3677
LP
43 bool in_list:1; /* In bus->tracks? */
44 bool in_queue:1; /* In bus->track_queue? */
ae1a2efa
LP
45 bool modified:1;
46 bool recursive:1;
232f3677
LP
47
48 LIST_FIELDS(sd_bus_track, tracks);
8f8f05a9
LP
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); \
f8294e41 66 _x = alloca(strlen(MATCH_PREFIX)+_l+strlen(MATCH_SUFFIX)+1); \
8f8f05a9
LP
67 strcpy(stpcpy(stpcpy(_x, MATCH_PREFIX), name), MATCH_SUFFIX); \
68 _x; \
69 })
70
ae1a2efa
LP
71static 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);
6b430fdb 78 return mfree(i);
ae1a2efa
LP
79}
80
81DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item*, track_item_free);
82
8f8f05a9
LP
83static void bus_track_add_to_queue(sd_bus_track *track) {
84 assert(track);
85
8b6e65ac
LP
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? */
8f8f05a9
LP
90 if (track->in_queue)
91 return;
92
8b6e65ac
LP
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? */
8f8f05a9
LP
103 if (!track->handler)
104 return;
105
232f3677
LP
106 /* Already closed? */
107 if (!track->in_list)
108 return;
109
8f8f05a9
LP
110 LIST_PREPEND(queue, track->bus->track_queue, track);
111 track->in_queue = true;
112}
113
114static 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
ae1a2efa
LP
124static 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
8b6e65ac 136 bus_track_add_to_queue(track);
ae1a2efa
LP
137
138 track->modified = true;
139 return 1;
140}
141
8f8f05a9
LP
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
33c62dcb
LP
153 if (!bus->bus_client)
154 return -EINVAL;
155
8f8f05a9
LP
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
232f3677
LP
165 LIST_PREPEND(tracks, bus->tracks, t);
166 t->in_list = true;
167
8f8f05a9
LP
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) {
4afd3348
LP
175
176 if (!track)
177 return NULL;
8f8f05a9
LP
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) {
ae1a2efa 187 struct track_item *i;
8f8f05a9
LP
188
189 if (!track)
190 return NULL;
191
192 assert(track->n_ref > 0);
193
194 if (track->n_ref > 1) {
313cefa1 195 track->n_ref--;
8f8f05a9
LP
196 return NULL;
197 }
198
ae1a2efa
LP
199 while ((i = hashmap_steal_first(track->names)))
200 track_item_free(i);
8f8f05a9 201
232f3677
LP
202 if (track->in_list)
203 LIST_REMOVE(tracks, track->bus->tracks, track);
204
8f8f05a9 205 bus_track_remove_from_queue(track);
19befb2d 206 hashmap_free(track->names);
8f8f05a9 207 sd_bus_unref(track->bus);
6b430fdb 208 return mfree(track);
8f8f05a9
LP
209}
210
19070062 211static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
8f8f05a9
LP
212 sd_bus_track *track = userdata;
213 const char *name, *old, *new;
214 int r;
215
8f8f05a9
LP
216 assert(message);
217 assert(track);
218
219 r = sd_bus_message_read(message, "sss", &name, &old, &new);
220 if (r < 0)
221 return 0;
222
ae1a2efa 223 bus_track_remove_name_fully(track, name);
8f8f05a9
LP
224 return 0;
225}
226
227_public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) {
ae1a2efa
LP
228 _cleanup_(track_item_freep) struct track_item *n = NULL;
229 struct track_item *i;
8f8f05a9
LP
230 const char *match;
231 int r;
232
233 assert_return(track, -EINVAL);
234 assert_return(service_name_is_valid(name), -EINVAL);
235
ae1a2efa
LP
236 i = hashmap_get(track->names, name);
237 if (i) {
238 if (track->recursive) {
239 unsigned k = track->n_ref + 1;
240
241 if (k < track->n_ref) /* Check for overflow */
242 return -EOVERFLOW;
243
244 track->n_ref = k;
245 }
246
247 bus_track_remove_from_queue(track);
248 return 0;
249 }
250
d5099efc 251 r = hashmap_ensure_allocated(&track->names, &string_hash_ops);
8f8f05a9
LP
252 if (r < 0)
253 return r;
254
ae1a2efa 255 n = new0(struct track_item, 1);
8f8f05a9
LP
256 if (!n)
257 return -ENOMEM;
ae1a2efa
LP
258 n->name = strdup(name);
259 if (!n->name)
260 return -ENOMEM;
8f8f05a9 261
19befb2d 262 /* First, subscribe to this name */
ae1a2efa 263 match = MATCH_FOR_NAME(name);
8b6e65ac
LP
264
265 bus_track_remove_from_queue(track); /* don't dispatch this while we work in it */
266
267 track->n_adding++; /* make sure we aren't dispatched while we synchronously add this match */
ae1a2efa 268 r = sd_bus_add_match(track->bus, &n->slot, match, on_name_owner_changed, track);
8b6e65ac
LP
269 track->n_adding--;
270 if (r < 0) {
271 bus_track_add_to_queue(track);
19befb2d 272 return r;
8b6e65ac 273 }
19befb2d 274
ae1a2efa 275 r = hashmap_put(track->names, n->name, n);
8b6e65ac
LP
276 if (r < 0) {
277 bus_track_add_to_queue(track);
8f8f05a9 278 return r;
8b6e65ac 279 }
8f8f05a9 280
ae1a2efa 281 /* Second, check if it is currently existing, or maybe doesn't, or maybe disappeared already. */
8b6e65ac 282 track->n_adding++; /* again, make sure this isn't dispatch while we are working in it */
ae1a2efa 283 r = sd_bus_get_name_creds(track->bus, name, 0, NULL);
8b6e65ac 284 track->n_adding--;
8f8f05a9 285 if (r < 0) {
ae1a2efa 286 hashmap_remove(track->names, name);
8b6e65ac 287 bus_track_add_to_queue(track);
8f8f05a9
LP
288 return r;
289 }
290
ae1a2efa 291 n->n_ref = 1;
8f8f05a9
LP
292 n = NULL;
293
294 bus_track_remove_from_queue(track);
295 track->modified = true;
296
297 return 1;
298}
299
300_public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) {
ae1a2efa 301 struct track_item *i;
8f8f05a9
LP
302
303 assert_return(name, -EINVAL);
304
ae1a2efa 305 if (!track) /* Treat a NULL track object as an empty track object */
8f8f05a9
LP
306 return 0;
307
ae1a2efa
LP
308 if (!track->recursive)
309 return bus_track_remove_name_fully(track, name);
8f8f05a9 310
ae1a2efa
LP
311 i = hashmap_get(track->names, name);
312 if (!i)
313 return -EUNATCH;
314 if (i->n_ref <= 0)
315 return -EUNATCH;
8f8f05a9 316
ae1a2efa
LP
317 i->n_ref--;
318
319 if (i->n_ref <= 0)
320 return bus_track_remove_name_fully(track, name);
8f8f05a9
LP
321
322 return 1;
323}
324
325_public_ unsigned sd_bus_track_count(sd_bus_track *track) {
ae1a2efa
LP
326
327 if (!track) /* Let's consider a NULL object equivalent to an empty object */
8f8f05a9
LP
328 return 0;
329
ae1a2efa
LP
330 /* This signature really should have returned an int, so that we can propagate errors. But well, ... Also, note
331 * that this returns the number of names being watched, and multiple references to the same name are not
332 * counted. */
333
19befb2d 334 return hashmap_size(track->names);
8f8f05a9
LP
335}
336
337_public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) {
8f8f05a9
LP
338 assert_return(name, NULL);
339
ae1a2efa
LP
340 if (!track) /* Let's consider a NULL object equivalent to an empty object */
341 return NULL;
342
19befb2d 343 return hashmap_get(track->names, (void*) name) ? name : NULL;
8f8f05a9
LP
344}
345
346_public_ const char* sd_bus_track_first(sd_bus_track *track) {
19befb2d
LP
347 const char *n = NULL;
348
8f8f05a9
LP
349 if (!track)
350 return NULL;
351
352 track->modified = false;
4dd6c572 353 track->iterator = ITERATOR_FIRST;
8f8f05a9 354
8927b1da 355 hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
19befb2d 356 return n;
8f8f05a9
LP
357}
358
359_public_ const char* sd_bus_track_next(sd_bus_track *track) {
19befb2d
LP
360 const char *n = NULL;
361
8f8f05a9
LP
362 if (!track)
363 return NULL;
364
365 if (track->modified)
366 return NULL;
367
8927b1da 368 hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
19befb2d 369 return n;
8f8f05a9
LP
370}
371
372_public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) {
373 const char *sender;
374
375 assert_return(track, -EINVAL);
376 assert_return(m, -EINVAL);
377
ae1a2efa
LP
378 if (sd_bus_message_get_bus(m) != track->bus)
379 return -EINVAL;
380
8f8f05a9
LP
381 sender = sd_bus_message_get_sender(m);
382 if (!sender)
383 return -EINVAL;
384
385 return sd_bus_track_add_name(track, sender);
386}
387
388_public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) {
389 const char *sender;
390
8f8f05a9
LP
391 assert_return(m, -EINVAL);
392
ae1a2efa
LP
393 if (!track) /* Treat a NULL track object as an empty track object */
394 return 0;
395
396 if (sd_bus_message_get_bus(m) != track->bus)
397 return -EINVAL;
398
8f8f05a9
LP
399 sender = sd_bus_message_get_sender(m);
400 if (!sender)
401 return -EINVAL;
402
403 return sd_bus_track_remove_name(track, sender);
404}
405
406_public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) {
407 assert_return(track, NULL);
408
409 return track->bus;
410}
411
412void bus_track_dispatch(sd_bus_track *track) {
413 int r;
414
415 assert(track);
8f8f05a9
LP
416 assert(track->handler);
417
418 bus_track_remove_from_queue(track);
419
420 sd_bus_track_ref(track);
421
422 r = track->handler(track, track->userdata);
423 if (r < 0)
da927ba9 424 log_debug_errno(r, "Failed to process track handler: %m");
8f8f05a9
LP
425 else if (r == 0)
426 bus_track_add_to_queue(track);
427
428 sd_bus_track_unref(track);
429}
04552566 430
232f3677
LP
431void bus_track_close(sd_bus_track *track) {
432 struct track_item *i;
433
434 assert(track);
435
436 /* Called whenever our bus connected is closed. If so, and our track object is non-empty, dispatch it
437 * immediately, as we are closing now, but first flush out all names. */
438
439 if (!track->in_list)
440 return; /* We already closed this one, don't close it again. */
441
442 /* Remember that this one is closed now */
443 LIST_REMOVE(tracks, track->bus->tracks, track);
444 track->in_list = false;
445
446 /* If there's no name in this one anyway, we don't have to dispatch */
447 if (hashmap_isempty(track->names))
448 return;
449
450 /* Let's flush out all names */
451 while ((i = hashmap_steal_first(track->names)))
452 track_item_free(i);
453
454 /* Invoke handler */
455 if (track->handler)
456 bus_track_dispatch(track);
457}
458
04552566
LP
459_public_ void *sd_bus_track_get_userdata(sd_bus_track *track) {
460 assert_return(track, NULL);
461
462 return track->userdata;
463}
464
465_public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) {
466 void *ret;
467
468 assert_return(track, NULL);
469
470 ret = track->userdata;
471 track->userdata = userdata;
472
473 return ret;
474}
ae1a2efa
LP
475
476_public_ int sd_bus_track_set_recursive(sd_bus_track *track, int b) {
477 assert_return(track, -EINVAL);
478
479 if (track->recursive == !!b)
480 return 0;
481
482 if (!hashmap_isempty(track->names))
483 return -EBUSY;
484
485 track->recursive = b;
486 return 0;
487}
488
489_public_ int sd_bus_track_get_recursive(sd_bus_track *track) {
490 assert_return(track, -EINVAL);
491
492 return track->recursive;
493}
494
495_public_ int sd_bus_track_count_sender(sd_bus_track *track, sd_bus_message *m) {
496 const char *sender;
497
498 assert_return(m, -EINVAL);
499
500 if (!track) /* Let's consider a NULL object equivalent to an empty object */
501 return 0;
502
503 if (sd_bus_message_get_bus(m) != track->bus)
504 return -EINVAL;
505
506 sender = sd_bus_message_get_sender(m);
507 if (!sender)
508 return -EINVAL;
509
510 return sd_bus_track_count_name(track, sender);
511}
512
513_public_ int sd_bus_track_count_name(sd_bus_track *track, const char *name) {
514 struct track_item *i;
515
516 assert_return(service_name_is_valid(name), -EINVAL);
517
518 if (!track) /* Let's consider a NULL object equivalent to an empty object */
519 return 0;
520
521 i = hashmap_get(track->names, name);
522 if (!i)
523 return 0;
524
525 return i->n_ref;
526}