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