/* * * Embedded Linux library * * Copyright (C) 2016 Intel Corporation. All rights reserved. * * This library 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. * * This library 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 this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include "util.h" #include "hashmap.h" #include "idle.h" #include "dbus.h" #include "dbus-private.h" struct _dbus_name_cache { struct l_dbus *bus; struct l_hashmap *names; const struct _dbus_name_ops *driver; unsigned int last_watch_id; struct l_idle *watch_remove_work; }; struct service_watch { l_dbus_watch_func_t connect_func; l_dbus_watch_func_t disconnect_func; l_dbus_destroy_func_t destroy; void *user_data; unsigned int id; struct service_watch *next; }; struct name_cache_entry { int ref_count; char *unique_name; struct service_watch *watches; }; struct _dbus_name_cache *_dbus_name_cache_new(struct l_dbus *bus, const struct _dbus_name_ops *driver) { struct _dbus_name_cache *cache; cache = l_new(struct _dbus_name_cache, 1); cache->bus = bus; cache->driver = driver; return cache; } static void service_watch_destroy(void *data) { struct service_watch *watch = data; if (watch->destroy) watch->destroy(watch->user_data); l_free(watch); } static void name_cache_entry_destroy(void *data) { struct name_cache_entry *entry = data; struct service_watch *watch; while (entry->watches) { watch = entry->watches; entry->watches = watch->next; service_watch_destroy(watch); } l_free(entry->unique_name); l_free(entry); } void _dbus_name_cache_free(struct _dbus_name_cache *cache) { if (!cache) return; if (cache->watch_remove_work) l_idle_remove(cache->watch_remove_work); l_hashmap_destroy(cache->names, name_cache_entry_destroy); l_free(cache); } bool _dbus_name_cache_add(struct _dbus_name_cache *cache, const char *name) { struct name_cache_entry *entry; if (!_dbus_valid_bus_name(name)) return false; if (!cache->names) cache->names = l_hashmap_string_new(); entry = l_hashmap_lookup(cache->names, name); if (!entry) { entry = l_new(struct name_cache_entry, 1); l_hashmap_insert(cache->names, name, entry); cache->driver->get_name_owner(cache->bus, name); } entry->ref_count++; return true; } bool _dbus_name_cache_remove(struct _dbus_name_cache *cache, const char *name) { struct name_cache_entry *entry; entry = l_hashmap_lookup(cache->names, name); if (!entry) return false; if (--entry->ref_count) return true; l_hashmap_remove(cache->names, name); name_cache_entry_destroy(entry); return true; } const char *_dbus_name_cache_lookup(struct _dbus_name_cache *cache, const char *name) { struct name_cache_entry *entry; entry = l_hashmap_lookup(cache->names, name); if (!entry) return NULL; return entry->unique_name; } void _dbus_name_cache_notify(struct _dbus_name_cache *cache, const char *name, const char *owner) { struct name_cache_entry *entry; struct service_watch *watch; bool prev_connected, connected; if (!cache) return; entry = l_hashmap_lookup(cache->names, name); if (!entry) return; prev_connected = !!entry->unique_name; connected = owner && *owner != '\0'; l_free(entry->unique_name); entry->unique_name = connected ? l_strdup(owner) : NULL; /* * This check also means we notify all watchers who have a connected * callback when we first learn that the service is in fact connected. */ if (connected == prev_connected) return; for (watch = entry->watches; watch; watch = watch->next) if (connected && watch->connect_func) watch->connect_func(cache->bus, watch->user_data); else if (!connected && watch->disconnect_func) watch->disconnect_func(cache->bus, watch->user_data); } unsigned int _dbus_name_cache_add_watch(struct _dbus_name_cache *cache, const char *name, l_dbus_watch_func_t connect_func, l_dbus_watch_func_t disconnect_func, void *user_data, l_dbus_destroy_func_t destroy) { struct name_cache_entry *entry; struct service_watch *watch; if (!_dbus_name_cache_add(cache, name)) return 0; watch = l_new(struct service_watch, 1); watch->id = ++cache->last_watch_id; watch->connect_func = connect_func; watch->disconnect_func = disconnect_func; watch->user_data = user_data; watch->destroy = destroy; entry = l_hashmap_lookup(cache->names, name); watch->next = entry->watches; entry->watches = watch; if (entry->unique_name && connect_func) watch->connect_func(cache->bus, watch->user_data); return watch->id; } static bool service_watch_remove(const void *key, void *value, void *user_data) { struct name_cache_entry *entry = value; struct service_watch **watch, *tmp; for (watch = &entry->watches; *watch;) { if ((*watch)->id) { watch = &(*watch)->next; continue; } tmp = *watch; *watch = tmp->next; service_watch_destroy(tmp); entry->ref_count--; } if (entry->ref_count) return false; name_cache_entry_destroy(entry); return true; } static void service_watch_remove_all(struct l_idle *idle, void *user_data) { struct _dbus_name_cache *cache = user_data; l_idle_remove(cache->watch_remove_work); cache->watch_remove_work = NULL; l_hashmap_foreach_remove(cache->names, service_watch_remove, cache); } static void service_watch_mark(const void *key, void *value, void *user_data) { struct name_cache_entry *entry = value; struct service_watch *watch; unsigned int *id = user_data; if (!*id) return; for (watch = entry->watches; watch; watch = watch->next) if (watch->id == *id) { watch->id = 0; watch->connect_func = NULL; watch->disconnect_func = NULL; if (watch->destroy) { watch->destroy(watch->user_data); watch->destroy = NULL; } *id = 0; break; } } bool _dbus_name_cache_remove_watch(struct _dbus_name_cache *cache, unsigned int id) { l_hashmap_foreach(cache->names, service_watch_mark, &id); if (id) return false; if (!cache->watch_remove_work) cache->watch_remove_work = l_idle_create( service_watch_remove_all, cache, NULL); return true; }