8b1bbb87167af20e5cd870a05292ee8adc20e2d6
[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 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 = 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 the 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,
69                                  dbus_int32_t          *slot_id_p)
70 {
71   dbus_int32_t slot;
72
73   if (!_dbus_mutex_lock (mutex))
74     return FALSE;
75
76   if (allocator->n_allocated_slots == 0)
77     {
78       _dbus_assert (allocator->lock == NULL);
79       allocator->lock = mutex;
80     }
81   else if (allocator->lock != mutex)
82     {
83       _dbus_warn ("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.");
84       _dbus_abort ();
85     }
86
87   if (*slot_id_p >= 0)
88     {
89       slot = *slot_id_p;
90       
91       _dbus_assert (slot < allocator->n_allocated_slots);
92       _dbus_assert (allocator->allocated_slots[slot].slot_id == slot);
93       
94       allocator->allocated_slots[slot].refcount += 1;
95
96       goto out;
97     }
98
99   _dbus_assert (*slot_id_p < 0);
100   
101   if (allocator->n_used_slots < allocator->n_allocated_slots)
102     {
103       slot = 0;
104       while (slot < allocator->n_allocated_slots)
105         {
106           if (allocator->allocated_slots[slot].slot_id < 0)
107             {
108               allocator->allocated_slots[slot].slot_id = slot;
109               allocator->allocated_slots[slot].refcount = 1;
110               allocator->n_used_slots += 1;
111               break;
112             }
113           ++slot;
114         }
115
116       _dbus_assert (slot < allocator->n_allocated_slots);
117     }
118   else
119     {
120       DBusAllocatedSlot *tmp;
121       
122       slot = -1;
123       tmp = dbus_realloc (allocator->allocated_slots,
124                           sizeof (DBusAllocatedSlot) * (allocator->n_allocated_slots + 1));
125       if (tmp == NULL)
126         goto out;
127
128       allocator->allocated_slots = tmp;
129       slot = allocator->n_allocated_slots;
130       allocator->n_allocated_slots += 1;
131       allocator->n_used_slots += 1;
132       allocator->allocated_slots[slot].slot_id = slot;
133       allocator->allocated_slots[slot].refcount = 1;
134     }
135
136   _dbus_assert (slot >= 0);
137   _dbus_assert (slot < allocator->n_allocated_slots);
138   _dbus_assert (*slot_id_p < 0);
139   _dbus_assert (allocator->allocated_slots[slot].slot_id == slot);
140   _dbus_assert (allocator->allocated_slots[slot].refcount == 1);
141   
142   *slot_id_p = slot;
143   
144   _dbus_verbose ("Allocated slot %d on allocator %p total %d slots allocated %d used\n",
145                  slot, allocator, allocator->n_allocated_slots, allocator->n_used_slots);
146   
147  out:
148   _dbus_mutex_unlock (allocator->lock);
149   return slot >= 0;
150 }
151
152 /**
153  * Deallocates an ID previously allocated with
154  * _dbus_data_slot_allocator_alloc().  Existing data stored on
155  * existing #DBusDataList objects with this ID will be freed when the
156  * data list is finalized, but may not be retrieved (and may only be
157  * replaced if someone else reallocates the slot).
158  * The slot value is reset to -1 if this is the last unref.
159  *
160  * @param allocator the allocator
161  * @param slot_id_p address where we store the slot
162  */
163 void
164 _dbus_data_slot_allocator_free (DBusDataSlotAllocator *allocator,
165                                 dbus_int32_t          *slot_id_p)
166 {
167   _dbus_mutex_lock (allocator->lock);
168   
169   _dbus_assert (*slot_id_p < allocator->n_allocated_slots);
170   _dbus_assert (allocator->allocated_slots[*slot_id_p].slot_id == *slot_id_p);
171   _dbus_assert (allocator->allocated_slots[*slot_id_p].refcount > 0);
172
173   allocator->allocated_slots[*slot_id_p].refcount -= 1;
174
175   if (allocator->allocated_slots[*slot_id_p].refcount > 0)
176     {
177       _dbus_mutex_unlock (allocator->lock);
178       return;
179     }
180
181   /* refcount is 0, free the slot */
182   _dbus_verbose ("Freeing slot %d on allocator %p total %d allocated %d used\n",
183                  *slot_id_p, allocator, allocator->n_allocated_slots, allocator->n_used_slots);
184   
185   allocator->allocated_slots[*slot_id_p].slot_id = -1;
186   *slot_id_p = -1;
187   
188   allocator->n_used_slots -= 1;
189   
190   if (allocator->n_used_slots == 0)
191     {
192       DBusMutex *mutex = allocator->lock;
193       
194       dbus_free (allocator->allocated_slots);
195       allocator->allocated_slots = NULL;
196       allocator->n_allocated_slots = 0;
197       allocator->lock = NULL;
198
199       _dbus_mutex_unlock (mutex);
200     }
201   else
202     {
203       _dbus_mutex_unlock (allocator->lock);
204     }
205 }
206
207 /**
208  * Initializes a slot list.
209  * @param list the list to initialize.
210  */
211 void
212 _dbus_data_slot_list_init (DBusDataSlotList *list)
213 {
214   list->slots = NULL;
215   list->n_slots = 0;
216 }
217
218 /**
219  * Stores a pointer in the data slot list, along with an optional
220  * function to be used for freeing the data when the data is set
221  * again, or when the slot list is finalized. The slot number must
222  * have been allocated with _dbus_data_slot_allocator_alloc() for the
223  * same allocator passed in here. The same allocator has to be used
224  * with the slot list every time.
225  *
226  * @param allocator the allocator to use
227  * @param list the data slot list
228  * @param slot the slot number
229  * @param data the data to store
230  * @param free_data_func finalizer function for the data
231  * @param old_free_func free function for any previously-existing data
232  * @param old_data previously-existing data, should be freed with old_free_func
233  * @returns #TRUE if there was enough memory to store the data
234  */
235 dbus_bool_t
236 _dbus_data_slot_list_set  (DBusDataSlotAllocator *allocator,
237                            DBusDataSlotList      *list,
238                            int                    slot,
239                            void                  *data,
240                            DBusFreeFunction       free_data_func,
241                            DBusFreeFunction      *old_free_func,
242                            void                 **old_data)
243 {
244 #ifndef DBUS_DISABLE_ASSERT
245   /* We need to take the allocator lock here, because the allocator could
246    * be e.g. realloc()ing allocated_slots. We avoid doing this if asserts
247    * are disabled, since then the asserts are empty.
248    */
249   if (!_dbus_mutex_lock (allocator->lock))
250     return FALSE;
251   _dbus_assert (slot < allocator->n_allocated_slots);
252   _dbus_assert (allocator->allocated_slots[slot].slot_id == slot);
253   _dbus_mutex_unlock (allocator->lock);
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   if (!_dbus_mutex_lock (allocator->lock))
308     return FALSE;
309   _dbus_assert (slot >= 0);
310   _dbus_assert (slot < allocator->n_allocated_slots);
311   _dbus_assert (allocator->allocated_slots[slot].slot_id == slot);
312   _dbus_mutex_unlock (allocator->lock);
313 #endif
314
315   if (slot >= list->n_slots)
316     return NULL;
317   else
318     return list->slots[slot].data;
319 }
320
321 /**
322  * Frees all data slots contained in the list, calling
323  * application-provided free functions if they exist.
324  *
325  * @param list the list to clear
326  */
327 void
328 _dbus_data_slot_list_clear (DBusDataSlotList *list)
329 {
330   int i;
331
332   i = 0;
333   while (i < list->n_slots)
334     {
335       if (list->slots[i].free_data_func)
336         (* list->slots[i].free_data_func) (list->slots[i].data);
337       list->slots[i].data = NULL;
338       list->slots[i].free_data_func = NULL;
339       ++i;
340     }
341 }
342
343 /**
344  * Frees the data slot list and all data slots contained
345  * in it, calling application-provided free functions
346  * if they exist.
347  *
348  * @param list the list to free
349  */
350 void
351 _dbus_data_slot_list_free (DBusDataSlotList *list)
352 {
353   _dbus_data_slot_list_clear (list);
354   
355   dbus_free (list->slots);
356   list->slots = NULL;
357   list->n_slots = 0;
358 }
359
360 /** @} */
361
362 #ifdef DBUS_BUILD_TESTS
363 #include "dbus-test.h"
364 #include <stdio.h>
365
366 static int free_counter;
367
368 static void
369 test_free_slot_data_func (void *data)
370 {
371   int i = _DBUS_POINTER_TO_INT (data);
372
373   _dbus_assert (free_counter == i);
374   ++free_counter;
375 }
376
377 /**
378  * Test function for data slots
379  */
380 dbus_bool_t
381 _dbus_data_slot_test (void)
382 {
383   DBusDataSlotAllocator allocator;
384   DBusDataSlotList list;
385   int i;
386   DBusFreeFunction old_free_func;
387   void *old_data;
388   DBusMutex *mutex;
389   
390   if (!_dbus_data_slot_allocator_init (&allocator))
391     _dbus_assert_not_reached ("no memory for allocator");
392
393   _dbus_data_slot_list_init (&list);
394
395   mutex = _dbus_mutex_new ();
396   if (mutex == NULL)
397     _dbus_assert_not_reached ("failed to alloc mutex");
398   
399 #define N_SLOTS 100
400
401   i = 0;
402   while (i < N_SLOTS)
403     {
404       /* we don't really want apps to rely on this ordered
405        * allocation, but it simplifies things to rely on it
406        * here.
407        */
408       dbus_int32_t tmp = -1;
409       
410       _dbus_data_slot_allocator_alloc (&allocator, mutex, &tmp);
411
412       if (tmp != i)
413         _dbus_assert_not_reached ("did not allocate slots in numeric order\n");
414
415       ++i;
416     }
417
418   i = 0;
419   while (i < N_SLOTS)
420     {
421       if (!_dbus_data_slot_list_set (&allocator, &list,
422                                      i,
423                                      _DBUS_INT_TO_POINTER (i), 
424                                      test_free_slot_data_func,
425                                      &old_free_func, &old_data))
426         _dbus_assert_not_reached ("no memory to set data");
427
428       _dbus_assert (old_free_func == NULL);
429       _dbus_assert (old_data == NULL);
430
431       _dbus_assert (_dbus_data_slot_list_get (&allocator, &list, i) ==
432                     _DBUS_INT_TO_POINTER (i));
433       
434       ++i;
435     }
436
437   free_counter = 0;
438   i = 0;
439   while (i < N_SLOTS)
440     {
441       if (!_dbus_data_slot_list_set (&allocator, &list,
442                                      i,
443                                      _DBUS_INT_TO_POINTER (i), 
444                                      test_free_slot_data_func,
445                                      &old_free_func, &old_data))
446         _dbus_assert_not_reached ("no memory to set data");
447
448       _dbus_assert (old_free_func == test_free_slot_data_func);
449       _dbus_assert (_DBUS_POINTER_TO_INT (old_data) == i);
450
451       (* old_free_func) (old_data);
452       _dbus_assert (i == (free_counter - 1));
453
454       _dbus_assert (_dbus_data_slot_list_get (&allocator, &list, i) ==
455                     _DBUS_INT_TO_POINTER (i));
456       
457       ++i;
458     }
459
460   free_counter = 0;
461   _dbus_data_slot_list_free (&list);
462
463   _dbus_assert (N_SLOTS == free_counter);
464
465   i = 0;
466   while (i < N_SLOTS)
467     {
468       dbus_int32_t tmp = i;
469       
470       _dbus_data_slot_allocator_free (&allocator, &tmp);
471       _dbus_assert (tmp == -1);
472       ++i;
473     }
474
475   _dbus_mutex_free (mutex);
476   
477   return TRUE;
478 }
479
480 #endif /* DBUS_BUILD_TESTS */