Home | History | Annotate | Download | only in dbus
      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