1/* -*- mode: C; c-file-style: "gnu" -*- */ 2/* dbus-dataslot.c storing data on objects 3 * 4 * Copyright (C) 2003 Red Hat, Inc. 5 * 6 * Licensed under the Academic Free License version 2.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 * 22 */ 23#include "dbus-dataslot.h" 24#include "dbus-threads-internal.h" 25 26/** 27 * @defgroup DBusDataSlot Data slots 28 * @ingroup DBusInternals 29 * @brief Storing data by ID 30 * 31 * Types and functions related to storing data by an 32 * allocated ID. This is used for dbus_connection_set_data(), 33 * dbus_server_set_data(), etc. 34 * @{ 35 */ 36 37/** 38 * Initializes a data slot allocator object, used to assign 39 * integer IDs for data slots. 40 * 41 * @param allocator the allocator to initialize 42 */ 43dbus_bool_t 44_dbus_data_slot_allocator_init (DBusDataSlotAllocator *allocator) 45{ 46 allocator->allocated_slots = NULL; 47 allocator->n_allocated_slots = 0; 48 allocator->n_used_slots = 0; 49 allocator->lock_loc = NULL; 50 51 return TRUE; 52} 53 54/** 55 * Allocates an integer ID to be used for storing data 56 * in a #DBusDataSlotList. If the value at *slot_id_p is 57 * not -1, this function just increments the refcount for 58 * the existing slot ID. If the value is -1, a new slot ID 59 * is allocated and stored at *slot_id_p. 60 * 61 * @param allocator the allocator 62 * @param mutex_loc the location lock for this allocator 63 * @param slot_id_p address to fill with the slot ID 64 * @returns #TRUE on success 65 */ 66dbus_bool_t 67_dbus_data_slot_allocator_alloc (DBusDataSlotAllocator *allocator, 68 DBusMutex **mutex_loc, 69 dbus_int32_t *slot_id_p) 70{ 71 dbus_int32_t slot; 72 73 _dbus_mutex_lock (*mutex_loc); 74 75 if (allocator->n_allocated_slots == 0) 76 { 77 _dbus_assert (allocator->lock_loc == NULL); 78 allocator->lock_loc = mutex_loc; 79 } 80 else if (allocator->lock_loc != mutex_loc) 81 { 82 _dbus_warn_check_failed ("D-Bus threads were initialized after first using the D-Bus library. If your application does not directly initialize threads or use D-Bus, keep in mind that some library or plugin may have used D-Bus or initialized threads behind your back. You can often fix this problem by calling dbus_init_threads() or dbus_g_threads_init() early in your main() method, before D-Bus is used.\n"); 83 _dbus_assert_not_reached ("exiting"); 84 } 85 86 if (*slot_id_p >= 0) 87 { 88 slot = *slot_id_p; 89 90 _dbus_assert (slot < allocator->n_allocated_slots); 91 _dbus_assert (allocator->allocated_slots[slot].slot_id == slot); 92 93 allocator->allocated_slots[slot].refcount += 1; 94 95 goto out; 96 } 97 98 _dbus_assert (*slot_id_p < 0); 99 100 if (allocator->n_used_slots < allocator->n_allocated_slots) 101 { 102 slot = 0; 103 while (slot < allocator->n_allocated_slots) 104 { 105 if (allocator->allocated_slots[slot].slot_id < 0) 106 { 107 allocator->allocated_slots[slot].slot_id = slot; 108 allocator->allocated_slots[slot].refcount = 1; 109 allocator->n_used_slots += 1; 110 break; 111 } 112 ++slot; 113 } 114 115 _dbus_assert (slot < allocator->n_allocated_slots); 116 } 117 else 118 { 119 DBusAllocatedSlot *tmp; 120 121 slot = -1; 122 tmp = dbus_realloc (allocator->allocated_slots, 123 sizeof (DBusAllocatedSlot) * (allocator->n_allocated_slots + 1)); 124 if (tmp == NULL) 125 goto out; 126 127 allocator->allocated_slots = tmp; 128 slot = allocator->n_allocated_slots; 129 allocator->n_allocated_slots += 1; 130 allocator->n_used_slots += 1; 131 allocator->allocated_slots[slot].slot_id = slot; 132 allocator->allocated_slots[slot].refcount = 1; 133 } 134 135 _dbus_assert (slot >= 0); 136 _dbus_assert (slot < allocator->n_allocated_slots); 137 _dbus_assert (*slot_id_p < 0); 138 _dbus_assert (allocator->allocated_slots[slot].slot_id == slot); 139 _dbus_assert (allocator->allocated_slots[slot].refcount == 1); 140 141 *slot_id_p = slot; 142 143 _dbus_verbose ("Allocated slot %d on allocator %p total %d slots allocated %d used\n", 144 slot, allocator, allocator->n_allocated_slots, allocator->n_used_slots); 145 146 out: 147 _dbus_mutex_unlock (*(allocator->lock_loc)); 148 return slot >= 0; 149} 150 151/** 152 * Deallocates an ID previously allocated with 153 * _dbus_data_slot_allocator_alloc(). Existing data stored on 154 * existing #DBusDataSlotList objects with this ID will be freed when the 155 * data list is finalized, but may not be retrieved (and may only be 156 * replaced if someone else reallocates the slot). 157 * The slot value is reset to -1 if this is the last unref. 158 * 159 * @param allocator the allocator 160 * @param slot_id_p address where we store the slot 161 */ 162void 163_dbus_data_slot_allocator_free (DBusDataSlotAllocator *allocator, 164 dbus_int32_t *slot_id_p) 165{ 166 _dbus_mutex_lock (*(allocator->lock_loc)); 167 168 _dbus_assert (*slot_id_p < allocator->n_allocated_slots); 169 _dbus_assert (allocator->allocated_slots[*slot_id_p].slot_id == *slot_id_p); 170 _dbus_assert (allocator->allocated_slots[*slot_id_p].refcount > 0); 171 172 allocator->allocated_slots[*slot_id_p].refcount -= 1; 173 174 if (allocator->allocated_slots[*slot_id_p].refcount > 0) 175 { 176 _dbus_mutex_unlock (*(allocator->lock_loc)); 177 return; 178 } 179 180 /* refcount is 0, free the slot */ 181 _dbus_verbose ("Freeing slot %d on allocator %p total %d allocated %d used\n", 182 *slot_id_p, allocator, allocator->n_allocated_slots, allocator->n_used_slots); 183 184 allocator->allocated_slots[*slot_id_p].slot_id = -1; 185 *slot_id_p = -1; 186 187 allocator->n_used_slots -= 1; 188 189 if (allocator->n_used_slots == 0) 190 { 191 DBusMutex **mutex_loc = allocator->lock_loc; 192 193 dbus_free (allocator->allocated_slots); 194 allocator->allocated_slots = NULL; 195 allocator->n_allocated_slots = 0; 196 allocator->lock_loc = NULL; 197 198 _dbus_mutex_unlock (*mutex_loc); 199 } 200 else 201 { 202 _dbus_mutex_unlock (*(allocator->lock_loc)); 203 } 204} 205 206/** 207 * Initializes a slot list. 208 * @param list the list to initialize. 209 */ 210void 211_dbus_data_slot_list_init (DBusDataSlotList *list) 212{ 213 list->slots = NULL; 214 list->n_slots = 0; 215} 216 217/** 218 * Stores a pointer in the data slot list, along with an optional 219 * function to be used for freeing the data when the data is set 220 * again, or when the slot list is finalized. The slot number must 221 * have been allocated with _dbus_data_slot_allocator_alloc() for the 222 * same allocator passed in here. The same allocator has to be used 223 * with the slot list every time. 224 * 225 * @param allocator the allocator to use 226 * @param list the data slot list 227 * @param slot the slot number 228 * @param data the data to store 229 * @param free_data_func finalizer function for the data 230 * @param old_free_func free function for any previously-existing data 231 * @param old_data previously-existing data, should be freed with old_free_func 232 * @returns #TRUE if there was enough memory to store the data 233 */ 234dbus_bool_t 235_dbus_data_slot_list_set (DBusDataSlotAllocator *allocator, 236 DBusDataSlotList *list, 237 int slot, 238 void *data, 239 DBusFreeFunction free_data_func, 240 DBusFreeFunction *old_free_func, 241 void **old_data) 242{ 243#ifndef DBUS_DISABLE_ASSERT 244 /* We need to take the allocator lock here, because the allocator could 245 * be e.g. realloc()ing allocated_slots. We avoid doing this if asserts 246 * are disabled, since then the asserts are empty. 247 */ 248 _dbus_mutex_lock (*(allocator->lock_loc)); 249 _dbus_assert (slot < allocator->n_allocated_slots); 250 _dbus_assert (allocator->allocated_slots[slot].slot_id == slot); 251 _dbus_mutex_unlock (*(allocator->lock_loc)); 252#endif 253 254 if (slot >= list->n_slots) 255 { 256 DBusDataSlot *tmp; 257 int i; 258 259 tmp = dbus_realloc (list->slots, 260 sizeof (DBusDataSlot) * (slot + 1)); 261 if (tmp == NULL) 262 return FALSE; 263 264 list->slots = tmp; 265 i = list->n_slots; 266 list->n_slots = slot + 1; 267 while (i < list->n_slots) 268 { 269 list->slots[i].data = NULL; 270 list->slots[i].free_data_func = NULL; 271 ++i; 272 } 273 } 274 275 _dbus_assert (slot < list->n_slots); 276 277 *old_data = list->slots[slot].data; 278 *old_free_func = list->slots[slot].free_data_func; 279 280 list->slots[slot].data = data; 281 list->slots[slot].free_data_func = free_data_func; 282 283 return TRUE; 284} 285 286/** 287 * Retrieves data previously set with _dbus_data_slot_list_set_data(). 288 * The slot must still be allocated (must not have been freed). 289 * 290 * @param allocator the allocator slot was allocated from 291 * @param list the data slot list 292 * @param slot the slot to get data from 293 * @returns the data, or #NULL if not found 294 */ 295void* 296_dbus_data_slot_list_get (DBusDataSlotAllocator *allocator, 297 DBusDataSlotList *list, 298 int slot) 299{ 300#ifndef DBUS_DISABLE_ASSERT 301 /* We need to take the allocator lock here, because the allocator could 302 * be e.g. realloc()ing allocated_slots. We avoid doing this if asserts 303 * are disabled, since then the asserts are empty. 304 */ 305 _dbus_mutex_lock (*(allocator->lock_loc)); 306 _dbus_assert (slot >= 0); 307 _dbus_assert (slot < allocator->n_allocated_slots); 308 _dbus_assert (allocator->allocated_slots[slot].slot_id == slot); 309 _dbus_mutex_unlock (*(allocator->lock_loc)); 310#endif 311 312 if (slot >= list->n_slots) 313 return NULL; 314 else 315 return list->slots[slot].data; 316} 317 318/** 319 * Frees all data slots contained in the list, calling 320 * application-provided free functions if they exist. 321 * 322 * @param list the list to clear 323 */ 324void 325_dbus_data_slot_list_clear (DBusDataSlotList *list) 326{ 327 int i; 328 329 i = 0; 330 while (i < list->n_slots) 331 { 332 if (list->slots[i].free_data_func) 333 (* list->slots[i].free_data_func) (list->slots[i].data); 334 list->slots[i].data = NULL; 335 list->slots[i].free_data_func = NULL; 336 ++i; 337 } 338} 339 340/** 341 * Frees the data slot list and all data slots contained 342 * in it, calling application-provided free functions 343 * if they exist. 344 * 345 * @param list the list to free 346 */ 347void 348_dbus_data_slot_list_free (DBusDataSlotList *list) 349{ 350 _dbus_data_slot_list_clear (list); 351 352 dbus_free (list->slots); 353 list->slots = NULL; 354 list->n_slots = 0; 355} 356 357/** @} */ 358 359#ifdef DBUS_BUILD_TESTS 360#include "dbus-test.h" 361#include <stdio.h> 362 363static int free_counter; 364 365static void 366test_free_slot_data_func (void *data) 367{ 368 int i = _DBUS_POINTER_TO_INT (data); 369 370 _dbus_assert (free_counter == i); 371 ++free_counter; 372} 373 374/** 375 * Test function for data slots 376 */ 377dbus_bool_t 378_dbus_data_slot_test (void) 379{ 380 DBusDataSlotAllocator allocator; 381 DBusDataSlotList list; 382 int i; 383 DBusFreeFunction old_free_func; 384 void *old_data; 385 DBusMutex *mutex; 386 387 if (!_dbus_data_slot_allocator_init (&allocator)) 388 _dbus_assert_not_reached ("no memory for allocator"); 389 390 _dbus_data_slot_list_init (&list); 391 392 _dbus_mutex_new_at_location (&mutex); 393 if (mutex == NULL) 394 _dbus_assert_not_reached ("failed to alloc mutex"); 395 396#define N_SLOTS 100 397 398 i = 0; 399 while (i < N_SLOTS) 400 { 401 /* we don't really want apps to rely on this ordered 402 * allocation, but it simplifies things to rely on it 403 * here. 404 */ 405 dbus_int32_t tmp = -1; 406 407 _dbus_data_slot_allocator_alloc (&allocator, &mutex, &tmp); 408 409 if (tmp != i) 410 _dbus_assert_not_reached ("did not allocate slots in numeric order\n"); 411 412 ++i; 413 } 414 415 i = 0; 416 while (i < N_SLOTS) 417 { 418 if (!_dbus_data_slot_list_set (&allocator, &list, 419 i, 420 _DBUS_INT_TO_POINTER (i), 421 test_free_slot_data_func, 422 &old_free_func, &old_data)) 423 _dbus_assert_not_reached ("no memory to set data"); 424 425 _dbus_assert (old_free_func == NULL); 426 _dbus_assert (old_data == NULL); 427 428 _dbus_assert (_dbus_data_slot_list_get (&allocator, &list, i) == 429 _DBUS_INT_TO_POINTER (i)); 430 431 ++i; 432 } 433 434 free_counter = 0; 435 i = 0; 436 while (i < N_SLOTS) 437 { 438 if (!_dbus_data_slot_list_set (&allocator, &list, 439 i, 440 _DBUS_INT_TO_POINTER (i), 441 test_free_slot_data_func, 442 &old_free_func, &old_data)) 443 _dbus_assert_not_reached ("no memory to set data"); 444 445 _dbus_assert (old_free_func == test_free_slot_data_func); 446 _dbus_assert (_DBUS_POINTER_TO_INT (old_data) == i); 447 448 (* old_free_func) (old_data); 449 _dbus_assert (i == (free_counter - 1)); 450 451 _dbus_assert (_dbus_data_slot_list_get (&allocator, &list, i) == 452 _DBUS_INT_TO_POINTER (i)); 453 454 ++i; 455 } 456 457 free_counter = 0; 458 _dbus_data_slot_list_free (&list); 459 460 _dbus_assert (N_SLOTS == free_counter); 461 462 i = 0; 463 while (i < N_SLOTS) 464 { 465 dbus_int32_t tmp = i; 466 467 _dbus_data_slot_allocator_free (&allocator, &tmp); 468 _dbus_assert (tmp == -1); 469 ++i; 470 } 471 472 _dbus_mutex_free_at_location (&mutex); 473 474 return TRUE; 475} 476 477#endif /* DBUS_BUILD_TESTS */ 478