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