chiark / gitweb /
tree-wide: drop 'This file is part of systemd' blurb
[elogind.git] / src / libelogind / sd-bus / bus-track.c
index 12ab53b7b71e24a00e94baa3e6ea3723dcaa51ed..846556cb36096666ef6e293b6e12fce15f103e27 100644 (file)
@@ -1,70 +1,86 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  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 "alloc-util.h"
 #include "bus-internal.h"
 #include "bus-track.h"
+#include "bus-util.h"
+
+struct track_item {
+        unsigned n_ref;
+        char *name;
+        sd_bus_slot *slot;
+};
 
 struct sd_bus_track {
         unsigned n_ref;
+        unsigned n_adding; /* are we in the process of adding a new name? */
         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;
+        bool in_list:1;    /* In bus->tracks? */
+        bool in_queue:1;   /* In bus->track_queue? */
+        bool modified:1;
+        bool recursive:1;
+        sd_bus_destroy_t destroy_callback;
+
+        LIST_FIELDS(sd_bus_track, tracks);
 };
 
-#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;                                                     \
-        })
+#define MATCH_FOR_NAME(name)                            \
+        strjoina("type='signal',"                       \
+                 "sender='org.freedesktop.DBus',"       \
+                 "path='/org/freedesktop/DBus',"        \
+                 "interface='org.freedesktop.DBus',"    \
+                 "member='NameOwnerChanged',"           \
+                 "arg0='", name, "'")
+
+static struct track_item* track_item_free(struct track_item *i) {
+
+        if (!i)
+                return NULL;
+
+        sd_bus_slot_unref(i->slot);
+        free(i->name);
+        return mfree(i);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item*, track_item_free);
 
 static void bus_track_add_to_queue(sd_bus_track *track) {
         assert(track);
 
+        /* Adds the bus track object to the queue of objects we should dispatch next, subject to a number of
+         * conditions. */
+
+        /* Already in the queue? */
         if (track->in_queue)
                 return;
 
+        /* if we are currently in the process of adding a new name, then let's not enqueue this just yet, let's wait
+         * until the addition is complete. */
+        if (track->n_adding > 0)
+                return;
+
+        /* still referenced? */
+        if (hashmap_size(track->names) > 0)
+                return;
+
+        /* Nothing to call? */
         if (!track->handler)
                 return;
 
+        /* Already closed? */
+        if (!track->in_list)
+                return;
+
         LIST_PREPEND(queue, track->bus->track_queue, track);
         track->in_queue = true;
 }
@@ -79,6 +95,24 @@ static void bus_track_remove_from_queue(sd_bus_track *track) {
         track->in_queue = false;
 }
 
+static int bus_track_remove_name_fully(sd_bus_track *track, const char *name) {
+        struct track_item *i;
+
+        assert(track);
+        assert(name);
+
+        i = hashmap_remove(track->names, name);
+        if (!i)
+                return 0;
+
+        track_item_free(i);
+
+        bus_track_add_to_queue(track);
+
+        track->modified = true;
+        return 1;
+}
+
 _public_ int sd_bus_track_new(
                 sd_bus *bus,
                 sd_bus_track **track,
@@ -88,6 +122,7 @@ _public_ int sd_bus_track_new(
         sd_bus_track *t;
 
         assert_return(bus, -EINVAL);
+        assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(track, -EINVAL);
 
         if (!bus->bus_client)
@@ -102,6 +137,9 @@ _public_ int sd_bus_track_new(
         t->userdata = userdata;
         t->bus = sd_bus_ref(bus);
 
+        LIST_PREPEND(tracks, bus->tracks, t);
+        t->in_list = true;
+
         bus_track_add_to_queue(t);
 
         *track = t;
@@ -109,7 +147,9 @@ _public_ int sd_bus_track_new(
 }
 
 _public_ sd_bus_track* sd_bus_track_ref(sd_bus_track *track) {
-        assert_return(track, NULL);
+
+        if (!track)
+                return NULL;
 
         assert(track->n_ref > 0);
 
@@ -119,27 +159,26 @@ _public_ sd_bus_track* sd_bus_track_ref(sd_bus_track *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);
+        track->n_ref--;
 
-        if (track->n_ref > 1) {
-                track->n_ref --;
+        if (track->n_ref > 0)
                 return NULL;
-        }
 
-        while ((n = hashmap_first_key(track->names)))
-                sd_bus_track_remove_name(track, n);
+        if (track->in_list)
+                LIST_REMOVE(tracks, track->bus->tracks, track);
 
         bus_track_remove_from_queue(track);
-        hashmap_free(track->names);
-        sd_bus_unref(track->bus);
-        free(track);
+        track->names = hashmap_free_with_destructor(track->names, track_item_free);
+        track->bus = sd_bus_unref(track->bus);
+
+        if (track->destroy_callback)
+                track->destroy_callback(track->userdata);
 
-        return NULL;
+        return mfree(track);
 }
 
 static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
@@ -154,49 +193,74 @@ static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus
         if (r < 0)
                 return 0;
 
-        sd_bus_track_remove_name(track, name);
+        bus_track_remove_name_fully(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;
+        _cleanup_(track_item_freep) struct track_item *n = NULL;
+        struct track_item *i;
         const char *match;
         int r;
 
         assert_return(track, -EINVAL);
         assert_return(service_name_is_valid(name), -EINVAL);
 
+        i = hashmap_get(track->names, name);
+        if (i) {
+                if (track->recursive) {
+                        unsigned k = track->n_ref + 1;
+
+                        if (k < track->n_ref) /* Check for overflow */
+                                return -EOVERFLOW;
+
+                        track->n_ref = k;
+                }
+
+                bus_track_remove_from_queue(track);
+                return 0;
+        }
+
         r = hashmap_ensure_allocated(&track->names, &string_hash_ops);
         if (r < 0)
                 return r;
 
-        n = strdup(name);
+        n = new0(struct track_item, 1);
         if (!n)
                 return -ENOMEM;
+        n->name = strdup(name);
+        if (!n->name)
+                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)
+        match = MATCH_FOR_NAME(name);
+
+        bus_track_remove_from_queue(track); /* don't dispatch this while we work in it */
+
+        r = sd_bus_add_match_async(track->bus, &n->slot, match, on_name_owner_changed, NULL, track);
+        if (r < 0) {
+                bus_track_add_to_queue(track);
                 return r;
+        }
 
-        r = hashmap_put(track->names, n, slot);
-        if (r == -EEXIST)
-                return 0;
-        if (r < 0)
+        r = hashmap_put(track->names, n->name, n);
+        if (r < 0) {
+                bus_track_add_to_queue(track);
                 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);
+        /* Second, check if it is currently existing, or maybe doesn't, or maybe disappeared already. */
+        track->n_adding++; /* again, make sure this isn't dispatch while we are working in it */
+        r = sd_bus_get_name_creds(track->bus, name, 0, NULL);
+        track->n_adding--;
         if (r < 0) {
-                hashmap_remove(track->names, n);
+                hashmap_remove(track->names, name);
+                bus_track_add_to_queue(track);
                 return r;
         }
 
+        n->n_ref = 1;
         n = NULL;
-        slot = NULL;
 
         bus_track_remove_from_queue(track);
         track->modified = true;
@@ -205,37 +269,48 @@ _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) {
 }
 
 _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;
+        struct track_item *i;
 
         assert_return(name, -EINVAL);
 
-        if (!track)
+        if (!track) /* Treat a NULL track object as an empty track object */
                 return 0;
 
-        slot = hashmap_remove2(track->names, (char*) name, (void**) &n);
-        if (!slot)
-                return 0;
+        if (!track->recursive)
+                return bus_track_remove_name_fully(track, name);
 
-        if (hashmap_isempty(track->names))
-                bus_track_add_to_queue(track);
+        i = hashmap_get(track->names, name);
+        if (!i)
+                return -EUNATCH;
+        if (i->n_ref <= 0)
+                return -EUNATCH;
 
-        track->modified = true;
+        i->n_ref--;
+
+        if (i->n_ref <= 0)
+                return bus_track_remove_name_fully(track, name);
 
         return 1;
 }
 
 _public_ unsigned sd_bus_track_count(sd_bus_track *track) {
-        if (!track)
+
+        if (!track) /* Let's consider a NULL object equivalent to an empty object */
                 return 0;
 
+        /* This signature really should have returned an int, so that we can propagate errors. But well, ... Also, note
+         * that this returns the number of names being watched, and multiple references to the same name are not
+         * counted. */
+
         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);
 
+        if (!track) /* Let's consider a NULL object equivalent to an empty object */
+                return NULL;
+
         return hashmap_get(track->names, (void*) name) ? name : NULL;
 }
 
@@ -271,6 +346,9 @@ _public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) {
         assert_return(track, -EINVAL);
         assert_return(m, -EINVAL);
 
+        if (sd_bus_message_get_bus(m) != track->bus)
+                return -EINVAL;
+
         sender = sd_bus_message_get_sender(m);
         if (!sender)
                 return -EINVAL;
@@ -281,9 +359,14 @@ _public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) {
 _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);
 
+        if (!track) /* Treat a NULL track object as an empty track object */
+                return 0;
+
+        if (sd_bus_message_get_bus(m) != track->bus)
+                return -EINVAL;
+
         sender = sd_bus_message_get_sender(m);
         if (!sender)
                 return -EINVAL;
@@ -301,7 +384,6 @@ 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);
@@ -317,8 +399,31 @@ void bus_track_dispatch(sd_bus_track *track) {
         sd_bus_track_unref(track);
 }
 
-/// UNNEEDED by elogind
-#if 0
+void bus_track_close(sd_bus_track *track) {
+        assert(track);
+
+        /* Called whenever our bus connected is closed. If so, and our track object is non-empty, dispatch it
+         * immediately, as we are closing now, but first flush out all names. */
+
+        if (!track->in_list)
+                return; /* We already closed this one, don't close it again. */
+
+        /* Remember that this one is closed now */
+        LIST_REMOVE(tracks, track->bus->tracks, track);
+        track->in_list = false;
+
+        /* If there's no name in this one anyway, we don't have to dispatch */
+        if (hashmap_isempty(track->names))
+                return;
+
+        /* Let's flush out all names */
+        hashmap_clear_with_destructor(track->names, track_item_free);
+
+        /* Invoke handler */
+        if (track->handler)
+                bus_track_dispatch(track);
+}
+
 _public_ void *sd_bus_track_get_userdata(sd_bus_track *track) {
         assert_return(track, NULL);
 
@@ -335,4 +440,71 @@ _public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) {
 
         return ret;
 }
-#endif // 0
+
+_public_ int sd_bus_track_set_destroy_callback(sd_bus_track *track, sd_bus_destroy_t callback) {
+        assert_return(track, -EINVAL);
+
+        track->destroy_callback = callback;
+        return 0;
+}
+
+_public_ int sd_bus_track_get_destroy_callback(sd_bus_track *track, sd_bus_destroy_t *ret) {
+        assert_return(track, -EINVAL);
+
+        if (ret)
+                *ret = track->destroy_callback;
+
+        return !!track->destroy_callback;
+}
+
+_public_ int sd_bus_track_set_recursive(sd_bus_track *track, int b) {
+        assert_return(track, -EINVAL);
+
+        if (track->recursive == !!b)
+                return 0;
+
+        if (!hashmap_isempty(track->names))
+                return -EBUSY;
+
+        track->recursive = b;
+        return 0;
+}
+
+_public_ int sd_bus_track_get_recursive(sd_bus_track *track) {
+        assert_return(track, -EINVAL);
+
+        return track->recursive;
+}
+
+_public_ int sd_bus_track_count_sender(sd_bus_track *track, sd_bus_message *m) {
+        const char *sender;
+
+        assert_return(m, -EINVAL);
+
+        if (!track) /* Let's consider a NULL object equivalent to an empty object */
+                return 0;
+
+        if (sd_bus_message_get_bus(m) != track->bus)
+                return -EINVAL;
+
+        sender = sd_bus_message_get_sender(m);
+        if (!sender)
+                return -EINVAL;
+
+        return sd_bus_track_count_name(track, sender);
+}
+
+_public_ int sd_bus_track_count_name(sd_bus_track *track, const char *name) {
+        struct track_item *i;
+
+        assert_return(service_name_is_valid(name), -EINVAL);
+
+        if (!track) /* Let's consider a NULL object equivalent to an empty object */
+                return 0;
+
+        i = hashmap_get(track->names, name);
+        if (!i)
+                return 0;
+
+        return i->n_ref;
+}