Home | History | Annotate | Download | only in dbus
      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  */
     43 dbus_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  */
     66 dbus_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  */
    162 void
    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  */
    210 void
    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  */
    234 dbus_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  */
    295 void*
    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  */
    324 void
    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  */
    347 void
    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 
    363 static int free_counter;
    364 
    365 static void
    366 test_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  */
    377 dbus_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