chiark / gitweb /
Rename more things to elogind
[elogind.git] / src / libelogind / sd-bus / bus-track.c
diff --git a/src/libelogind/sd-bus/bus-track.c b/src/libelogind/sd-bus/bus-track.c
new file mode 100644 (file)
index 0000000..0481785
--- /dev/null
@@ -0,0 +1,333 @@
+/*-*- 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 "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_ops);
+        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_name_creds(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 = ITERATOR_FIRST;
+
+        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_errno(r, "Failed to process track handler: %m");
+        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;
+}