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 */
45dbus_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 */
68dbus_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 */
164void
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 */
212void
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 */
236dbus_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 */
297void*
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 */
326void
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 */
349void
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
365static int free_counter;
366
367static void
368test_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 */
379dbus_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