19befb2d5f
This makes callback behaviour more like sd-event or sd-resolve, and creates proper object for unregistering callbacks. Taking the refernce to the slot is optional. If not taken life time of the slot will be bound to the underlying bus object (or in the case of an async call until the reply has been recieved).
335 lines
8.7 KiB
C
335 lines
8.7 KiB
C
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
|
|
|
/***
|
|
This file is part of systemd.
|
|
|
|
Copyright 2013 Lennart Poettering
|
|
|
|
systemd is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation; either version 2.1 of the License, or
|
|
(at your option) any later version.
|
|
|
|
systemd is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
|
***/
|
|
|
|
#include "sd-bus.h"
|
|
#include "set.h"
|
|
#include "bus-util.h"
|
|
#include "bus-internal.h"
|
|
#include "bus-track.h"
|
|
|
|
struct sd_bus_track {
|
|
unsigned n_ref;
|
|
sd_bus *bus;
|
|
sd_bus_track_handler_t handler;
|
|
void *userdata;
|
|
Hashmap *names;
|
|
LIST_FIELDS(sd_bus_track, queue);
|
|
Iterator iterator;
|
|
bool in_queue;
|
|
bool modified;
|
|
};
|
|
|
|
#define MATCH_PREFIX \
|
|
"type='signal'," \
|
|
"sender='org.freedesktop.DBus'," \
|
|
"path='/org/freedesktop/DBus'," \
|
|
"interface='org.freedesktop.DBus'," \
|
|
"member='NameOwnerChanged'," \
|
|
"arg0='"
|
|
|
|
#define MATCH_SUFFIX \
|
|
"'"
|
|
|
|
#define MATCH_FOR_NAME(name) \
|
|
({ \
|
|
char *_x; \
|
|
size_t _l = strlen(name); \
|
|
_x = alloca(strlen(MATCH_PREFIX)+_l+strlen(MATCH_SUFFIX)+1); \
|
|
strcpy(stpcpy(stpcpy(_x, MATCH_PREFIX), name), MATCH_SUFFIX); \
|
|
_x; \
|
|
})
|
|
|
|
static void bus_track_add_to_queue(sd_bus_track *track) {
|
|
assert(track);
|
|
|
|
if (track->in_queue)
|
|
return;
|
|
|
|
if (!track->handler)
|
|
return;
|
|
|
|
LIST_PREPEND(queue, track->bus->track_queue, track);
|
|
track->in_queue = true;
|
|
}
|
|
|
|
static void bus_track_remove_from_queue(sd_bus_track *track) {
|
|
assert(track);
|
|
|
|
if (!track->in_queue)
|
|
return;
|
|
|
|
LIST_REMOVE(queue, track->bus->track_queue, track);
|
|
track->in_queue = false;
|
|
}
|
|
|
|
_public_ int sd_bus_track_new(
|
|
sd_bus *bus,
|
|
sd_bus_track **track,
|
|
sd_bus_track_handler_t handler,
|
|
void *userdata) {
|
|
|
|
sd_bus_track *t;
|
|
|
|
assert_return(bus, -EINVAL);
|
|
assert_return(track, -EINVAL);
|
|
|
|
t = new0(sd_bus_track, 1);
|
|
if (!t)
|
|
return -ENOMEM;
|
|
|
|
t->n_ref = 1;
|
|
t->handler = handler;
|
|
t->userdata = userdata;
|
|
t->bus = sd_bus_ref(bus);
|
|
|
|
bus_track_add_to_queue(t);
|
|
|
|
*track = t;
|
|
return 0;
|
|
}
|
|
|
|
_public_ sd_bus_track* sd_bus_track_ref(sd_bus_track *track) {
|
|
assert_return(track, NULL);
|
|
|
|
assert(track->n_ref > 0);
|
|
|
|
track->n_ref++;
|
|
|
|
return track;
|
|
}
|
|
|
|
_public_ sd_bus_track* sd_bus_track_unref(sd_bus_track *track) {
|
|
const char *n;
|
|
|
|
if (!track)
|
|
return NULL;
|
|
|
|
assert(track->n_ref > 0);
|
|
|
|
if (track->n_ref > 1) {
|
|
track->n_ref --;
|
|
return NULL;
|
|
}
|
|
|
|
while ((n = hashmap_first_key(track->names)))
|
|
sd_bus_track_remove_name(track, n);
|
|
|
|
bus_track_remove_from_queue(track);
|
|
hashmap_free(track->names);
|
|
sd_bus_unref(track->bus);
|
|
free(track);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int on_name_owner_changed(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
|
sd_bus_track *track = userdata;
|
|
const char *name, *old, *new;
|
|
int r;
|
|
|
|
assert(bus);
|
|
assert(message);
|
|
assert(track);
|
|
|
|
r = sd_bus_message_read(message, "sss", &name, &old, &new);
|
|
if (r < 0)
|
|
return 0;
|
|
|
|
sd_bus_track_remove_name(track, name);
|
|
return 0;
|
|
}
|
|
|
|
_public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) {
|
|
_cleanup_bus_slot_unref_ sd_bus_slot *slot = NULL;
|
|
_cleanup_free_ char *n = NULL;
|
|
const char *match;
|
|
int r;
|
|
|
|
assert_return(track, -EINVAL);
|
|
assert_return(service_name_is_valid(name), -EINVAL);
|
|
|
|
r = hashmap_ensure_allocated(&track->names, string_hash_func, string_compare_func);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
n = strdup(name);
|
|
if (!n)
|
|
return -ENOMEM;
|
|
|
|
/* First, subscribe to this name */
|
|
match = MATCH_FOR_NAME(n);
|
|
r = sd_bus_add_match(track->bus, &slot, match, on_name_owner_changed, track);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = hashmap_put(track->names, n, slot);
|
|
if (r == -EEXIST)
|
|
return 0;
|
|
if (r < 0)
|
|
return r;
|
|
|
|
/* Second, check if it is currently existing, or maybe
|
|
* doesn't, or maybe disappeared already. */
|
|
r = sd_bus_get_owner(track->bus, n, 0, NULL);
|
|
if (r < 0) {
|
|
hashmap_remove(track->names, n);
|
|
return r;
|
|
}
|
|
|
|
n = NULL;
|
|
slot = NULL;
|
|
|
|
bus_track_remove_from_queue(track);
|
|
track->modified = true;
|
|
|
|
return 1;
|
|
}
|
|
|
|
_public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) {
|
|
_cleanup_bus_slot_unref_ sd_bus_slot *slot = NULL;
|
|
_cleanup_free_ char *n = NULL;
|
|
|
|
assert_return(name, -EINVAL);
|
|
|
|
if (!track)
|
|
return 0;
|
|
|
|
slot = hashmap_remove2(track->names, (char*) name, (void**) &n);
|
|
if (!slot)
|
|
return 0;
|
|
|
|
if (hashmap_isempty(track->names))
|
|
bus_track_add_to_queue(track);
|
|
|
|
track->modified = true;
|
|
|
|
return 1;
|
|
}
|
|
|
|
_public_ unsigned sd_bus_track_count(sd_bus_track *track) {
|
|
if (!track)
|
|
return 0;
|
|
|
|
return hashmap_size(track->names);
|
|
}
|
|
|
|
_public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) {
|
|
assert_return(track, NULL);
|
|
assert_return(name, NULL);
|
|
|
|
return hashmap_get(track->names, (void*) name) ? name : NULL;
|
|
}
|
|
|
|
_public_ const char* sd_bus_track_first(sd_bus_track *track) {
|
|
const char *n = NULL;
|
|
|
|
if (!track)
|
|
return NULL;
|
|
|
|
track->modified = false;
|
|
track->iterator = NULL;
|
|
|
|
hashmap_iterate(track->names, &track->iterator, (const void**) &n);
|
|
return n;
|
|
}
|
|
|
|
_public_ const char* sd_bus_track_next(sd_bus_track *track) {
|
|
const char *n = NULL;
|
|
|
|
if (!track)
|
|
return NULL;
|
|
|
|
if (track->modified)
|
|
return NULL;
|
|
|
|
hashmap_iterate(track->names, &track->iterator, (const void**) &n);
|
|
return n;
|
|
}
|
|
|
|
_public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) {
|
|
const char *sender;
|
|
|
|
assert_return(track, -EINVAL);
|
|
assert_return(m, -EINVAL);
|
|
|
|
sender = sd_bus_message_get_sender(m);
|
|
if (!sender)
|
|
return -EINVAL;
|
|
|
|
return sd_bus_track_add_name(track, sender);
|
|
}
|
|
|
|
_public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) {
|
|
const char *sender;
|
|
|
|
assert_return(track, -EINVAL);
|
|
assert_return(m, -EINVAL);
|
|
|
|
sender = sd_bus_message_get_sender(m);
|
|
if (!sender)
|
|
return -EINVAL;
|
|
|
|
return sd_bus_track_remove_name(track, sender);
|
|
}
|
|
|
|
_public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) {
|
|
assert_return(track, NULL);
|
|
|
|
return track->bus;
|
|
}
|
|
|
|
void bus_track_dispatch(sd_bus_track *track) {
|
|
int r;
|
|
|
|
assert(track);
|
|
assert(track->in_queue);
|
|
assert(track->handler);
|
|
|
|
bus_track_remove_from_queue(track);
|
|
|
|
sd_bus_track_ref(track);
|
|
|
|
r = track->handler(track, track->userdata);
|
|
if (r < 0)
|
|
log_debug("Failed to process track handler: %s", strerror(-r));
|
|
else if (r == 0)
|
|
bus_track_add_to_queue(track);
|
|
|
|
sd_bus_track_unref(track);
|
|
}
|
|
|
|
_public_ void *sd_bus_track_get_userdata(sd_bus_track *track) {
|
|
assert_return(track, NULL);
|
|
|
|
return track->userdata;
|
|
}
|
|
|
|
_public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) {
|
|
void *ret;
|
|
|
|
assert_return(track, NULL);
|
|
|
|
ret = track->userdata;
|
|
track->userdata = userdata;
|
|
|
|
return ret;
|
|
}
|