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