2003-04-22 Havoc Pennington <hp@redhat.com>
[platform/upstream/dbus.git] / dbus / dbus-dataslot.c
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 1.2
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.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 = NULL;
50   
51   return TRUE;
52 }
53
54 /**
55  * Allocates an integer ID to be used for storing data
56  * in a #DBusDataSlotList.
57  *
58  * @todo all over the code we have foo_slot and foo_slot_refcount,
59  * would be better to add an interface for that to
60  * DBusDataSlotAllocator so it isn't cut-and-pasted everywhere.
61  * 
62  * @param allocator the allocator
63  * @param mutex the lock for this allocator
64  * @returns the integer ID, or -1 on failure
65  */
66 int
67 _dbus_data_slot_allocator_alloc (DBusDataSlotAllocator *allocator,
68                                  DBusMutex             *mutex)
69 {
70   int slot;
71
72   if (!dbus_mutex_lock (mutex))
73     return -1;
74
75   if (allocator->n_allocated_slots == 0)
76     {
77       _dbus_assert (allocator->lock == NULL);
78       allocator->lock = mutex;
79     }
80   else
81     _dbus_assert (allocator->lock == mutex);
82   
83   if (allocator->n_used_slots < allocator->n_allocated_slots)
84     {
85       slot = 0;
86       while (slot < allocator->n_allocated_slots)
87         {
88           if (allocator->allocated_slots[slot] < 0)
89             {
90               allocator->allocated_slots[slot] = slot;
91               allocator->n_used_slots += 1;
92               break;
93             }
94           ++slot;
95         }
96
97       _dbus_assert (slot < allocator->n_allocated_slots);
98     }
99   else
100     {
101       int *tmp;
102       
103       slot = -1;
104       tmp = dbus_realloc (allocator->allocated_slots,
105                           sizeof (int) * (allocator->n_allocated_slots + 1));
106       if (tmp == NULL)
107         goto out;
108
109       allocator->allocated_slots = tmp;
110       slot = allocator->n_allocated_slots;
111       allocator->n_allocated_slots += 1;
112       allocator->n_used_slots += 1;
113       allocator->allocated_slots[slot] = slot;
114     }
115
116   _dbus_assert (slot >= 0);
117   _dbus_assert (slot < allocator->n_allocated_slots);
118
119   _dbus_verbose ("Allocated slot %d on allocator %p total %d slots allocated %d used\n",
120                  slot, allocator, allocator->n_allocated_slots, allocator->n_used_slots);
121   
122  out:
123   dbus_mutex_unlock (allocator->lock);
124   return slot;
125 }
126
127 /**
128  * Deallocates an ID previously allocated with
129  * _dbus_data_slot_allocator_alloc().  Existing data stored on
130  * existing #DBusDataList objects with this ID will be freed when the
131  * data list is finalized, but may not be retrieved (and may only be
132  * replaced if someone else reallocates the slot).
133  *
134  * @param allocator the allocator
135  * @param slot the slot to deallocate
136  */
137 void
138 _dbus_data_slot_allocator_free (DBusDataSlotAllocator *allocator,
139                                 int                    slot)
140 {
141   dbus_mutex_lock (allocator->lock);
142   
143   _dbus_assert (slot < allocator->n_allocated_slots);
144   _dbus_assert (allocator->allocated_slots[slot] == slot);
145   
146   allocator->allocated_slots[slot] = -1;
147   allocator->n_used_slots -= 1;
148
149   _dbus_verbose ("Freed slot %d on allocator %p total %d allocated %d used\n",
150                  slot, allocator, allocator->n_allocated_slots, allocator->n_used_slots);
151   
152   if (allocator->n_used_slots == 0)
153     {
154       DBusMutex *mutex = allocator->lock;
155       
156       dbus_free (allocator->allocated_slots);
157       allocator->allocated_slots = NULL;
158       allocator->n_allocated_slots = 0;
159       allocator->lock = NULL;
160
161       dbus_mutex_unlock (mutex);
162     }
163   else
164     {
165       dbus_mutex_unlock (allocator->lock);
166     }
167 }
168
169 /**
170  * Initializes a slot list.
171  * @param list the list to initialize.
172  */
173 void
174 _dbus_data_slot_list_init (DBusDataSlotList *list)
175 {
176   list->slots = NULL;
177   list->n_slots = 0;
178 }
179
180 /**
181  * Stores a pointer in the data slot list, along with an optional
182  * function to be used for freeing the data when the data is set
183  * again, or when the slot list is finalized. The slot number must
184  * have been allocated with _dbus_data_slot_allocator_alloc() for the
185  * same allocator passed in here. The same allocator has to be used
186  * with the slot list every time.
187  *
188  * @param allocator the allocator to use
189  * @param list the data slot list
190  * @param slot the slot number
191  * @param data the data to store
192  * @param free_data_func finalizer function for the data
193  * @param old_free_func free function for any previously-existing data
194  * @param old_data previously-existing data, should be freed with old_free_func
195  * @returns #TRUE if there was enough memory to store the data
196  */
197 dbus_bool_t
198 _dbus_data_slot_list_set  (DBusDataSlotAllocator *allocator,
199                            DBusDataSlotList      *list,
200                            int                    slot,
201                            void                  *data,
202                            DBusFreeFunction       free_data_func,
203                            DBusFreeFunction      *old_free_func,
204                            void                 **old_data)
205 {
206 #ifndef DBUS_DISABLE_ASSERT
207   /* We need to take the allocator lock here, because the allocator could
208    * be e.g. realloc()ing allocated_slots. We avoid doing this if asserts
209    * are disabled, since then the asserts are empty.
210    */
211   if (!dbus_mutex_lock (allocator->lock))
212     return FALSE;
213   _dbus_assert (slot < allocator->n_allocated_slots);
214   _dbus_assert (allocator->allocated_slots[slot] == slot);
215   dbus_mutex_unlock (allocator->lock);
216 #endif
217   
218   if (slot >= list->n_slots)
219     {
220       DBusDataSlot *tmp;
221       int i;
222       
223       tmp = dbus_realloc (list->slots,
224                           sizeof (DBusDataSlot) * (slot + 1));
225       if (tmp == NULL)
226         return FALSE;
227       
228       list->slots = tmp;
229       i = list->n_slots;
230       list->n_slots = slot + 1;
231       while (i < list->n_slots)
232         {
233           list->slots[i].data = NULL;
234           list->slots[i].free_data_func = NULL;
235           ++i;
236         }
237     }
238
239   _dbus_assert (slot < list->n_slots);
240
241   *old_data = list->slots[slot].data;
242   *old_free_func = list->slots[slot].free_data_func;
243
244   list->slots[slot].data = data;
245   list->slots[slot].free_data_func = free_data_func;
246
247   return TRUE;
248 }
249
250 /**
251  * Retrieves data previously set with _dbus_data_slot_list_set_data().
252  * The slot must still be allocated (must not have been freed).
253  *
254  * @param allocator the allocator slot was allocated from
255  * @param list the data slot list
256  * @param slot the slot to get data from
257  * @returns the data, or #NULL if not found
258  */
259 void*
260 _dbus_data_slot_list_get  (DBusDataSlotAllocator *allocator,
261                            DBusDataSlotList      *list,
262                            int                    slot)
263 {
264 #ifndef DBUS_DISABLE_ASSERT
265   /* We need to take the allocator lock here, because the allocator could
266    * be e.g. realloc()ing allocated_slots. We avoid doing this if asserts
267    * are disabled, since then the asserts are empty.
268    */
269   if (!dbus_mutex_lock (allocator->lock))
270     return FALSE;
271   _dbus_assert (slot >= 0);
272   _dbus_assert (slot < allocator->n_allocated_slots);
273   _dbus_assert (allocator->allocated_slots[slot] == slot);
274   dbus_mutex_unlock (allocator->lock);
275 #endif
276
277   if (slot >= list->n_slots)
278     return NULL;
279   else
280     return list->slots[slot].data;
281 }
282
283 /**
284  * Frees the data slot list and all data slots contained
285  * in it, calling application-provided free functions
286  * if they exist.
287  *
288  * @param list the list to free
289  */
290 void
291 _dbus_data_slot_list_free (DBusDataSlotList *list)
292 {
293   int i;
294
295   i = 0;
296   while (i < list->n_slots)
297     {
298       if (list->slots[i].free_data_func)
299         (* list->slots[i].free_data_func) (list->slots[i].data);
300       list->slots[i].data = NULL;
301       list->slots[i].free_data_func = NULL;
302       ++i;
303     }
304
305   dbus_free (list->slots);
306   list->slots = NULL;
307   list->n_slots = 0;
308 }
309
310 /** @} */
311
312 #ifdef DBUS_BUILD_TESTS
313 #include "dbus-test.h"
314 #include <stdio.h>
315
316 static int free_counter;
317
318 static void
319 test_free_slot_data_func (void *data)
320 {
321   int i = _DBUS_POINTER_TO_INT (data);
322
323   _dbus_assert (free_counter == i);
324   ++free_counter;
325 }
326
327 /**
328  * Test function for data slots
329  */
330 dbus_bool_t
331 _dbus_data_slot_test (void)
332 {
333   DBusDataSlotAllocator allocator;
334   DBusDataSlotList list;
335   int i;
336   DBusFreeFunction old_free_func;
337   void *old_data;
338   DBusMutex *mutex;
339   
340   if (!_dbus_data_slot_allocator_init (&allocator))
341     _dbus_assert_not_reached ("no memory for allocator");
342
343   _dbus_data_slot_list_init (&list);
344
345   mutex = dbus_mutex_new ();
346   if (mutex == NULL)
347     _dbus_assert_not_reached ("failed to alloc mutex");
348   
349 #define N_SLOTS 100
350
351   i = 0;
352   while (i < N_SLOTS)
353     {
354       /* we don't really want apps to rely on this ordered
355        * allocation, but it simplifies things to rely on it
356        * here.
357        */
358       if (_dbus_data_slot_allocator_alloc (&allocator, mutex) != i)
359         _dbus_assert_not_reached ("did not allocate slots in numeric order\n");
360
361       ++i;
362     }
363
364   i = 0;
365   while (i < N_SLOTS)
366     {
367       if (!_dbus_data_slot_list_set (&allocator, &list,
368                                      i,
369                                      _DBUS_INT_TO_POINTER (i), 
370                                      test_free_slot_data_func,
371                                      &old_free_func, &old_data))
372         _dbus_assert_not_reached ("no memory to set data");
373
374       _dbus_assert (old_free_func == NULL);
375       _dbus_assert (old_data == NULL);
376
377       _dbus_assert (_dbus_data_slot_list_get (&allocator, &list, i) ==
378                     _DBUS_INT_TO_POINTER (i));
379       
380       ++i;
381     }
382
383   free_counter = 0;
384   i = 0;
385   while (i < N_SLOTS)
386     {
387       if (!_dbus_data_slot_list_set (&allocator, &list,
388                                      i,
389                                      _DBUS_INT_TO_POINTER (i), 
390                                      test_free_slot_data_func,
391                                      &old_free_func, &old_data))
392         _dbus_assert_not_reached ("no memory to set data");
393
394       _dbus_assert (old_free_func == test_free_slot_data_func);
395       _dbus_assert (_DBUS_POINTER_TO_INT (old_data) == i);
396
397       (* old_free_func) (old_data);
398       _dbus_assert (i == (free_counter - 1));
399
400       _dbus_assert (_dbus_data_slot_list_get (&allocator, &list, i) ==
401                     _DBUS_INT_TO_POINTER (i));
402       
403       ++i;
404     }
405
406   free_counter = 0;
407   _dbus_data_slot_list_free (&list);
408
409   _dbus_assert (N_SLOTS == free_counter);
410
411   i = 0;
412   while (i < N_SLOTS)
413     {
414       _dbus_data_slot_allocator_free (&allocator, i);
415       ++i;
416     }
417
418   dbus_mutex_free (mutex);
419   
420   return TRUE;
421 }
422
423 #endif /* DBUS_BUILD_TESTS */