1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2013 Lennart Poettering
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.
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.
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/>.
23 #include "alloc-util.h"
24 #include "bus-internal.h"
25 #include "bus-track.h"
36 unsigned n_adding
; /* are we in the process of adding a new name? */
38 sd_bus_track_handler_t handler
;
41 LIST_FIELDS(sd_bus_track
, queue
);
43 bool in_list
:1; /* In bus->tracks? */
44 bool in_queue
:1; /* In bus->track_queue? */
48 LIST_FIELDS(sd_bus_track
, tracks
);
51 #define MATCH_PREFIX \
53 "sender='org.freedesktop.DBus'," \
54 "path='/org/freedesktop/DBus'," \
55 "interface='org.freedesktop.DBus'," \
56 "member='NameOwnerChanged'," \
59 #define MATCH_SUFFIX \
62 #define MATCH_FOR_NAME(name) \
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); \
71 static struct track_item
* track_item_free(struct track_item
*i
) {
76 sd_bus_slot_unref(i
->slot
);
81 DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item
*, track_item_free
);
83 static void bus_track_add_to_queue(sd_bus_track
*track
) {
86 /* Adds the bus track object to the queue of objects we should dispatch next, subject to a number of
89 /* Already in the queue? */
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)
98 /* still referenced? */
99 if (hashmap_size(track
->names
) > 0)
102 /* Nothing to call? */
106 /* Already closed? */
110 LIST_PREPEND(queue
, track
->bus
->track_queue
, track
);
111 track
->in_queue
= true;
114 static void bus_track_remove_from_queue(sd_bus_track
*track
) {
117 if (!track
->in_queue
)
120 LIST_REMOVE(queue
, track
->bus
->track_queue
, track
);
121 track
->in_queue
= false;
124 static int bus_track_remove_name_fully(sd_bus_track
*track
, const char *name
) {
125 struct track_item
*i
;
130 i
= hashmap_remove(track
->names
, name
);
136 bus_track_add_to_queue(track
);
138 track
->modified
= true;
142 _public_
int sd_bus_track_new(
144 sd_bus_track
**track
,
145 sd_bus_track_handler_t handler
,
150 assert_return(bus
, -EINVAL
);
151 assert_return(track
, -EINVAL
);
153 if (!bus
->bus_client
)
156 t
= new0(sd_bus_track
, 1);
161 t
->handler
= handler
;
162 t
->userdata
= userdata
;
163 t
->bus
= sd_bus_ref(bus
);
165 LIST_PREPEND(tracks
, bus
->tracks
, t
);
168 bus_track_add_to_queue(t
);
174 _public_ sd_bus_track
* sd_bus_track_ref(sd_bus_track
*track
) {
179 assert(track
->n_ref
> 0);
186 _public_ sd_bus_track
* sd_bus_track_unref(sd_bus_track
*track
) {
190 assert(track
->n_ref
> 0);
192 if (track
->n_ref
> 1) {
198 LIST_REMOVE(tracks
, track
->bus
->tracks
, track
);
200 bus_track_remove_from_queue(track
);
201 hashmap_free_with_destructor(track
->names
, track_item_free
);
202 sd_bus_unref(track
->bus
);
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;
214 r
= sd_bus_message_read(message
, "sss", &name
, &old
, &new);
218 bus_track_remove_name_fully(track
, name
);
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
;
228 assert_return(track
, -EINVAL
);
229 assert_return(service_name_is_valid(name
), -EINVAL
);
231 i
= hashmap_get(track
->names
, name
);
233 if (track
->recursive
) {
234 unsigned k
= track
->n_ref
+ 1;
236 if (k
< track
->n_ref
) /* Check for overflow */
242 bus_track_remove_from_queue(track
);
246 r
= hashmap_ensure_allocated(&track
->names
, &string_hash_ops
);
250 n
= new0(struct track_item
, 1);
253 n
->name
= strdup(name
);
257 /* First, subscribe to this name */
258 match
= MATCH_FOR_NAME(name
);
260 bus_track_remove_from_queue(track
); /* don't dispatch this while we work in it */
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
);
266 bus_track_add_to_queue(track
);
270 r
= hashmap_put(track
->names
, n
->name
, n
);
272 bus_track_add_to_queue(track
);
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
);
281 hashmap_remove(track
->names
, name
);
282 bus_track_add_to_queue(track
);
289 bus_track_remove_from_queue(track
);
290 track
->modified
= true;
295 _public_
int sd_bus_track_remove_name(sd_bus_track
*track
, const char *name
) {
296 struct track_item
*i
;
298 assert_return(name
, -EINVAL
);
300 if (!track
) /* Treat a NULL track object as an empty track object */
303 if (!track
->recursive
)
304 return bus_track_remove_name_fully(track
, name
);
306 i
= hashmap_get(track
->names
, name
);
315 return bus_track_remove_name_fully(track
, name
);
320 _public_
unsigned sd_bus_track_count(sd_bus_track
*track
) {
322 if (!track
) /* Let's consider a NULL object equivalent to an empty object */
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
329 return hashmap_size(track
->names
);
332 _public_
const char* sd_bus_track_contains(sd_bus_track
*track
, const char *name
) {
333 assert_return(name
, NULL
);
335 if (!track
) /* Let's consider a NULL object equivalent to an empty object */
338 return hashmap_get(track
->names
, (void*) name
) ? name
: NULL
;
341 _public_
const char* sd_bus_track_first(sd_bus_track
*track
) {
342 const char *n
= NULL
;
347 track
->modified
= false;
348 track
->iterator
= ITERATOR_FIRST
;
350 hashmap_iterate(track
->names
, &track
->iterator
, NULL
, (const void**) &n
);
354 _public_
const char* sd_bus_track_next(sd_bus_track
*track
) {
355 const char *n
= NULL
;
363 hashmap_iterate(track
->names
, &track
->iterator
, NULL
, (const void**) &n
);
367 _public_
int sd_bus_track_add_sender(sd_bus_track
*track
, sd_bus_message
*m
) {
370 assert_return(track
, -EINVAL
);
371 assert_return(m
, -EINVAL
);
373 if (sd_bus_message_get_bus(m
) != track
->bus
)
376 sender
= sd_bus_message_get_sender(m
);
380 return sd_bus_track_add_name(track
, sender
);
383 _public_
int sd_bus_track_remove_sender(sd_bus_track
*track
, sd_bus_message
*m
) {
386 assert_return(m
, -EINVAL
);
388 if (!track
) /* Treat a NULL track object as an empty track object */
391 if (sd_bus_message_get_bus(m
) != track
->bus
)
394 sender
= sd_bus_message_get_sender(m
);
398 return sd_bus_track_remove_name(track
, sender
);
401 _public_ sd_bus
* sd_bus_track_get_bus(sd_bus_track
*track
) {
402 assert_return(track
, NULL
);
407 void bus_track_dispatch(sd_bus_track
*track
) {
411 assert(track
->handler
);
413 bus_track_remove_from_queue(track
);
415 sd_bus_track_ref(track
);
417 r
= track
->handler(track
, track
->userdata
);
419 log_debug_errno(r
, "Failed to process track handler: %m");
421 bus_track_add_to_queue(track
);
423 sd_bus_track_unref(track
);
426 void bus_track_close(sd_bus_track
*track
) {
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. */
433 return; /* We already closed this one, don't close it again. */
435 /* Remember that this one is closed now */
436 LIST_REMOVE(tracks
, track
->bus
->tracks
, track
);
437 track
->in_list
= false;
439 /* If there's no name in this one anyway, we don't have to dispatch */
440 if (hashmap_isempty(track
->names
))
443 /* Let's flush out all names */
444 hashmap_clear_with_destructor(track
->names
, track_item_free
);
448 bus_track_dispatch(track
);
451 _public_
void *sd_bus_track_get_userdata(sd_bus_track
*track
) {
452 assert_return(track
, NULL
);
454 return track
->userdata
;
457 _public_
void *sd_bus_track_set_userdata(sd_bus_track
*track
, void *userdata
) {
460 assert_return(track
, NULL
);
462 ret
= track
->userdata
;
463 track
->userdata
= userdata
;
468 _public_
int sd_bus_track_set_recursive(sd_bus_track
*track
, int b
) {
469 assert_return(track
, -EINVAL
);
471 if (track
->recursive
== !!b
)
474 if (!hashmap_isempty(track
->names
))
477 track
->recursive
= b
;
481 _public_
int sd_bus_track_get_recursive(sd_bus_track
*track
) {
482 assert_return(track
, -EINVAL
);
484 return track
->recursive
;
487 _public_
int sd_bus_track_count_sender(sd_bus_track
*track
, sd_bus_message
*m
) {
490 assert_return(m
, -EINVAL
);
492 if (!track
) /* Let's consider a NULL object equivalent to an empty object */
495 if (sd_bus_message_get_bus(m
) != track
->bus
)
498 sender
= sd_bus_message_get_sender(m
);
502 return sd_bus_track_count_name(track
, sender
);
505 _public_
int sd_bus_track_count_name(sd_bus_track
*track
, const char *name
) {
506 struct track_item
*i
;
508 assert_return(service_name_is_valid(name
), -EINVAL
);
510 if (!track
) /* Let's consider a NULL object equivalent to an empty object */
513 i
= hashmap_get(track
->names
, name
);