Home | History | Annotate | Download | only in gdbus
      1 /*
      2  *
      3  *  D-Bus helper library
      4  *
      5  *  Copyright (C) 2004-2009  Marcel Holtmann <marcel (at) holtmann.org>
      6  *
      7  *
      8  *  This program is free software; you can redistribute it and/or modify
      9  *  it under the terms of the GNU General Public License as published by
     10  *  the Free Software Foundation; either version 2 of the License, or
     11  *  (at your option) any later version.
     12  *
     13  *  This program is distributed in the hope that it will be useful,
     14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  *  GNU General Public License for more details.
     17  *
     18  *  You should have received a copy of the GNU General Public License
     19  *  along with this program; if not, write to the Free Software
     20  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     21  *
     22  */
     23 
     24 #ifdef HAVE_CONFIG_H
     25 #include <config.h>
     26 #endif
     27 
     28 #include <stdio.h>
     29 #include <string.h>
     30 
     31 #include <glib.h>
     32 #include <dbus/dbus.h>
     33 
     34 #include "gdbus.h"
     35 
     36 #define info(fmt...)
     37 #define error(fmt...)
     38 #define debug(fmt...)
     39 
     40 static DBusHandlerResult name_exit_filter(DBusConnection *connection,
     41 					DBusMessage *message, void *user_data);
     42 
     43 static guint listener_id = 0;
     44 static GSList *name_listeners = NULL;
     45 
     46 struct name_callback {
     47 	GDBusWatchFunction conn_func;
     48 	GDBusWatchFunction disc_func;
     49 	void *user_data;
     50 	guint id;
     51 };
     52 
     53 struct name_data {
     54 	DBusConnection *connection;
     55 	char *name;
     56 	GSList *callbacks;
     57 	GSList *processed;
     58 	gboolean lock;
     59 };
     60 
     61 static struct name_data *name_data_find(DBusConnection *connection,
     62 							const char *name)
     63 {
     64 	GSList *current;
     65 
     66 	for (current = name_listeners;
     67 			current != NULL; current = current->next) {
     68 		struct name_data *data = current->data;
     69 
     70 		if (connection != data->connection)
     71 			continue;
     72 
     73 		if (name == NULL || g_str_equal(name, data->name))
     74 			return data;
     75 	}
     76 
     77 	return NULL;
     78 }
     79 
     80 static struct name_callback *name_callback_find(GSList *callbacks, guint id)
     81 {
     82 	GSList *current;
     83 
     84 	for (current = callbacks; current != NULL; current = current->next) {
     85 		struct name_callback *cb = current->data;
     86 		if (cb->id == id)
     87 			return cb;
     88 	}
     89 
     90 	return NULL;
     91 }
     92 
     93 static void name_data_call_and_free(struct name_data *data)
     94 {
     95 	GSList *l;
     96 
     97 	for (l = data->callbacks; l != NULL; l = l->next) {
     98 		struct name_callback *cb = l->data;
     99 		if (cb->disc_func)
    100 			cb->disc_func(data->connection, cb->user_data);
    101 		g_free(cb);
    102 	}
    103 
    104 	g_slist_free(data->callbacks);
    105 	g_free(data->name);
    106 	g_free(data);
    107 }
    108 
    109 static void name_data_free(struct name_data *data)
    110 {
    111 	GSList *l;
    112 
    113 	for (l = data->callbacks; l != NULL; l = l->next)
    114 		g_free(l->data);
    115 
    116 	g_slist_free(data->callbacks);
    117 	g_free(data->name);
    118 	g_free(data);
    119 }
    120 
    121 static int name_data_add(DBusConnection *connection, const char *name,
    122 						GDBusWatchFunction connect,
    123 						GDBusWatchFunction disconnect,
    124 						void *user_data, guint id)
    125 {
    126 	int first = 1;
    127 	struct name_data *data = NULL;
    128 	struct name_callback *cb = NULL;
    129 
    130 	cb = g_new(struct name_callback, 1);
    131 
    132 	cb->conn_func = connect;
    133 	cb->disc_func = disconnect;
    134 	cb->user_data = user_data;
    135 	cb->id = id;
    136 
    137 	data = name_data_find(connection, name);
    138 	if (data) {
    139 		first = 0;
    140 		goto done;
    141 	}
    142 
    143 	data = g_new0(struct name_data, 1);
    144 
    145 	data->connection = connection;
    146 	data->name = g_strdup(name);
    147 
    148 	name_listeners = g_slist_append(name_listeners, data);
    149 
    150 done:
    151 	if (data->lock)
    152 		data->processed = g_slist_append(data->processed, cb);
    153 	else
    154 		data->callbacks = g_slist_append(data->callbacks, cb);
    155 
    156 	return first;
    157 }
    158 
    159 static void name_data_remove(DBusConnection *connection,
    160 					const char *name, guint id)
    161 {
    162 	struct name_data *data;
    163 	struct name_callback *cb = NULL;
    164 
    165 	data = name_data_find(connection, name);
    166 	if (!data)
    167 		return;
    168 
    169 	cb = name_callback_find(data->callbacks, id);
    170 	if (cb) {
    171 		data->callbacks = g_slist_remove(data->callbacks, cb);
    172 		g_free(cb);
    173 	}
    174 
    175 	if (data->callbacks)
    176 		return;
    177 
    178 	name_listeners = g_slist_remove(name_listeners, data);
    179 	name_data_free(data);
    180 
    181 	/* Remove filter if there are no listeners left for the connection */
    182 	data = name_data_find(connection, NULL);
    183 	if (!data)
    184 		dbus_connection_remove_filter(connection,
    185 						name_exit_filter,
    186 						NULL);
    187 }
    188 
    189 static gboolean add_match(DBusConnection *connection, const char *name)
    190 {
    191 	DBusError err;
    192 	char match_string[128];
    193 
    194 	snprintf(match_string, sizeof(match_string),
    195 			"interface=%s,member=NameOwnerChanged,arg0=%s",
    196 			DBUS_INTERFACE_DBUS, name);
    197 
    198 	dbus_error_init(&err);
    199 
    200 	dbus_bus_add_match(connection, match_string, &err);
    201 
    202 	if (dbus_error_is_set(&err)) {
    203 		error("Adding match rule \"%s\" failed: %s", match_string,
    204 				err.message);
    205 		dbus_error_free(&err);
    206 		return FALSE;
    207 	}
    208 
    209 	return TRUE;
    210 }
    211 
    212 static gboolean remove_match(DBusConnection *connection, const char *name)
    213 {
    214 	DBusError err;
    215 	char match_string[128];
    216 
    217 	snprintf(match_string, sizeof(match_string),
    218 			"interface=%s,member=NameOwnerChanged,arg0=%s",
    219 			DBUS_INTERFACE_DBUS, name);
    220 
    221 	dbus_error_init(&err);
    222 
    223 	dbus_bus_remove_match(connection, match_string, &err);
    224 
    225 	if (dbus_error_is_set(&err)) {
    226 		error("Removing owner match rule for %s failed: %s",
    227 				name, err.message);
    228 		dbus_error_free(&err);
    229 		return FALSE;
    230 	}
    231 
    232 	return TRUE;
    233 }
    234 
    235 static DBusHandlerResult name_exit_filter(DBusConnection *connection,
    236 					DBusMessage *message, void *user_data)
    237 {
    238 	struct name_data *data;
    239 	struct name_callback *cb;
    240 	char *name, *old, *new;
    241 
    242 	if (!dbus_message_is_signal(message, DBUS_INTERFACE_DBUS,
    243 							"NameOwnerChanged"))
    244 		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    245 
    246 	if (!dbus_message_get_args(message, NULL,
    247 				DBUS_TYPE_STRING, &name,
    248 				DBUS_TYPE_STRING, &old,
    249 				DBUS_TYPE_STRING, &new,
    250 				DBUS_TYPE_INVALID)) {
    251 		error("Invalid arguments for NameOwnerChanged signal");
    252 		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    253 	}
    254 
    255 	data = name_data_find(connection, name);
    256 	if (!data) {
    257 		error("Got NameOwnerChanged signal for %s which has no listeners", name);
    258 		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    259 	}
    260 
    261 	data->lock = TRUE;
    262 
    263 	while (data->callbacks) {
    264 		cb = data->callbacks->data;
    265 
    266 		if (*new == '\0') {
    267 			if (cb->disc_func)
    268 				cb->disc_func(connection, cb->user_data);
    269 		} else {
    270 			if (cb->conn_func)
    271 				cb->conn_func(connection, cb->user_data);
    272 		}
    273 
    274 		/* Check if the watch was removed/freed by the callback
    275 		 * function */
    276 		if (!g_slist_find(data->callbacks, cb))
    277 			continue;
    278 
    279 		data->callbacks = g_slist_remove(data->callbacks, cb);
    280 
    281 		if (!cb->conn_func || !cb->disc_func) {
    282 			g_free(cb);
    283 			continue;
    284 		}
    285 
    286 		data->processed = g_slist_append(data->processed, cb);
    287 	}
    288 
    289 	data->callbacks = data->processed;
    290 	data->processed = NULL;
    291 	data->lock = FALSE;
    292 
    293 	if (data->callbacks)
    294 		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    295 
    296 	name_listeners = g_slist_remove(name_listeners, data);
    297 	name_data_free(data);
    298 
    299 	/* Remove filter if there no listener left for the connection */
    300 	data = name_data_find(connection, NULL);
    301 	if (!data)
    302 		dbus_connection_remove_filter(connection, name_exit_filter,
    303 						NULL);
    304 
    305 	remove_match(connection, name);
    306 
    307 	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    308 }
    309 
    310 guint g_dbus_add_service_watch(DBusConnection *connection, const char *name,
    311 				GDBusWatchFunction connect,
    312 				GDBusWatchFunction disconnect,
    313 				void *user_data, GDBusDestroyFunction destroy)
    314 {
    315 	int first;
    316 
    317 	if (!name_data_find(connection, NULL)) {
    318 		if (!dbus_connection_add_filter(connection,
    319 					name_exit_filter, NULL, NULL)) {
    320 			error("dbus_connection_add_filter() failed");
    321 			return 0;
    322 		}
    323 	}
    324 
    325 	listener_id++;
    326 	first = name_data_add(connection, name, connect, disconnect,
    327 						user_data, listener_id);
    328 	/* The filter is already added if this is not the first callback
    329 	 * registration for the name */
    330 	if (!first)
    331 		return listener_id;
    332 
    333 	if (name) {
    334 		debug("name_listener_add(%s)", name);
    335 
    336 		if (!add_match(connection, name)) {
    337 			name_data_remove(connection, name, listener_id);
    338 			return 0;
    339 		}
    340 	}
    341 
    342 	return listener_id;
    343 }
    344 
    345 guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name,
    346 				GDBusWatchFunction func,
    347 				void *user_data, GDBusDestroyFunction destroy)
    348 {
    349 	return g_dbus_add_service_watch(connection, name, NULL, func,
    350 							user_data, destroy);
    351 }
    352 
    353 guint g_dbus_add_signal_watch(DBusConnection *connection,
    354 				const char *rule, GDBusSignalFunction function,
    355 				void *user_data, GDBusDestroyFunction destroy)
    356 {
    357 	return 0;
    358 }
    359 
    360 gboolean g_dbus_remove_watch(DBusConnection *connection, guint id)
    361 {
    362 	struct name_data *data;
    363 	struct name_callback *cb;
    364 	GSList *ldata, *lcb;
    365 
    366 	if (id == 0)
    367 		return FALSE;
    368 
    369 	for (ldata = name_listeners; ldata; ldata = ldata->next) {
    370 		data = ldata->data;
    371 		for (lcb = data->callbacks; lcb; lcb = lcb->next) {
    372 			cb = lcb->data;
    373 			if (cb->id == id)
    374 				goto remove;
    375 		}
    376 		for (lcb = data->processed; lcb; lcb = lcb->next) {
    377 			cb = lcb->data;
    378 			if (cb->id == id)
    379 				goto remove;
    380 		}
    381 	}
    382 
    383 	return FALSE;
    384 
    385 remove:
    386 	data->callbacks = g_slist_remove(data->callbacks, cb);
    387 	data->processed = g_slist_remove(data->processed, cb);
    388 	g_free(cb);
    389 
    390 	/* Don't remove the filter if other callbacks exist or data is lock
    391 	 * processing callbacks */
    392 	if (data->callbacks || data->lock)
    393 		return TRUE;
    394 
    395 	if (data->name) {
    396 		if (!remove_match(data->connection, data->name))
    397 			return FALSE;
    398 	}
    399 
    400 	name_listeners = g_slist_remove(name_listeners, data);
    401 	name_data_free(data);
    402 
    403 	/* Remove filter if there are no listeners left for the connection */
    404 	data = name_data_find(connection, NULL);
    405 	if (!data)
    406 		dbus_connection_remove_filter(connection, name_exit_filter,
    407 						NULL);
    408 
    409 	return TRUE;
    410 }
    411 
    412 void g_dbus_remove_all_watches(DBusConnection *connection)
    413 {
    414 	struct name_data *data;
    415 
    416 	while ((data = name_data_find(connection, NULL))) {
    417 		name_listeners = g_slist_remove(name_listeners, data);
    418 		name_data_call_and_free(data);
    419 	}
    420 
    421 	dbus_connection_remove_filter(connection, name_exit_filter, NULL);
    422 }
    423