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