| /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ |
| /* dbus-dataslot.c storing data on objects |
| * |
| * Copyright (C) 2003 Red Hat, Inc. |
| * |
| * Licensed under the Academic Free License version 2.1 |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #include <config.h> |
| #include "dbus-dataslot.h" |
| #include "dbus-threads-internal.h" |
| |
| /** |
| * @defgroup DBusDataSlot Data slots |
| * @ingroup DBusInternals |
| * @brief Storing data by ID |
| * |
| * Types and functions related to storing data by an |
| * allocated ID. This is used for dbus_connection_set_data(), |
| * dbus_server_set_data(), etc. |
| * @{ |
| */ |
| |
| /** |
| * Initializes a data slot allocator object, used to assign |
| * integer IDs for data slots. |
| * |
| * @param allocator the allocator to initialize |
| */ |
| dbus_bool_t |
| _dbus_data_slot_allocator_init (DBusDataSlotAllocator *allocator) |
| { |
| allocator->allocated_slots = NULL; |
| allocator->n_allocated_slots = 0; |
| allocator->n_used_slots = 0; |
| allocator->lock_loc = NULL; |
| |
| return TRUE; |
| } |
| |
| /** |
| * Allocates an integer ID to be used for storing data |
| * in a #DBusDataSlotList. If the value at *slot_id_p is |
| * not -1, this function just increments the refcount for |
| * the existing slot ID. If the value is -1, a new slot ID |
| * is allocated and stored at *slot_id_p. |
| * |
| * @param allocator the allocator |
| * @param mutex_loc the location lock for this allocator |
| * @param slot_id_p address to fill with the slot ID |
| * @returns #TRUE on success |
| */ |
| dbus_bool_t |
| _dbus_data_slot_allocator_alloc (DBusDataSlotAllocator *allocator, |
| DBusMutex **mutex_loc, |
| dbus_int32_t *slot_id_p) |
| { |
| dbus_int32_t slot; |
| |
| _dbus_mutex_lock (*mutex_loc); |
| |
| if (allocator->n_allocated_slots == 0) |
| { |
| _dbus_assert (allocator->lock_loc == NULL); |
| allocator->lock_loc = mutex_loc; |
| } |
| else if (allocator->lock_loc != mutex_loc) |
| { |
| _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"); |
| _dbus_assert_not_reached ("exiting"); |
| } |
| |
| if (*slot_id_p >= 0) |
| { |
| slot = *slot_id_p; |
| |
| _dbus_assert (slot < allocator->n_allocated_slots); |
| _dbus_assert (allocator->allocated_slots[slot].slot_id == slot); |
| |
| allocator->allocated_slots[slot].refcount += 1; |
| |
| goto out; |
| } |
| |
| _dbus_assert (*slot_id_p < 0); |
| |
| if (allocator->n_used_slots < allocator->n_allocated_slots) |
| { |
| slot = 0; |
| while (slot < allocator->n_allocated_slots) |
| { |
| if (allocator->allocated_slots[slot].slot_id < 0) |
| { |
| allocator->allocated_slots[slot].slot_id = slot; |
| allocator->allocated_slots[slot].refcount = 1; |
| allocator->n_used_slots += 1; |
| break; |
| } |
| ++slot; |
| } |
| |
| _dbus_assert (slot < allocator->n_allocated_slots); |
| } |
| else |
| { |
| DBusAllocatedSlot *tmp; |
| |
| slot = -1; |
| tmp = dbus_realloc (allocator->allocated_slots, |
| sizeof (DBusAllocatedSlot) * (allocator->n_allocated_slots + 1)); |
| if (tmp == NULL) |
| goto out; |
| |
| allocator->allocated_slots = tmp; |
| slot = allocator->n_allocated_slots; |
| allocator->n_allocated_slots += 1; |
| allocator->n_used_slots += 1; |
| allocator->allocated_slots[slot].slot_id = slot; |
| allocator->allocated_slots[slot].refcount = 1; |
| } |
| |
| _dbus_assert (slot >= 0); |
| _dbus_assert (slot < allocator->n_allocated_slots); |
| _dbus_assert (*slot_id_p < 0); |
| _dbus_assert (allocator->allocated_slots[slot].slot_id == slot); |
| _dbus_assert (allocator->allocated_slots[slot].refcount == 1); |
| |
| *slot_id_p = slot; |
| |
| _dbus_verbose ("Allocated slot %d on allocator %p total %d slots allocated %d used\n", |
| slot, allocator, allocator->n_allocated_slots, allocator->n_used_slots); |
| |
| out: |
| _dbus_mutex_unlock (*(allocator->lock_loc)); |
| return slot >= 0; |
| } |
| |
| /** |
| * Deallocates an ID previously allocated with |
| * _dbus_data_slot_allocator_alloc(). Existing data stored on |
| * existing #DBusDataSlotList objects with this ID will be freed when the |
| * data list is finalized, but may not be retrieved (and may only be |
| * replaced if someone else reallocates the slot). |
| * The slot value is reset to -1 if this is the last unref. |
| * |
| * @param allocator the allocator |
| * @param slot_id_p address where we store the slot |
| */ |
| void |
| _dbus_data_slot_allocator_free (DBusDataSlotAllocator *allocator, |
| dbus_int32_t *slot_id_p) |
| { |
| _dbus_mutex_lock (*(allocator->lock_loc)); |
| |
| _dbus_assert (*slot_id_p < allocator->n_allocated_slots); |
| _dbus_assert (allocator->allocated_slots[*slot_id_p].slot_id == *slot_id_p); |
| _dbus_assert (allocator->allocated_slots[*slot_id_p].refcount > 0); |
| |
| allocator->allocated_slots[*slot_id_p].refcount -= 1; |
| |
| if (allocator->allocated_slots[*slot_id_p].refcount > 0) |
| { |
| _dbus_mutex_unlock (*(allocator->lock_loc)); |
| return; |
| } |
| |
| /* refcount is 0, free the slot */ |
| _dbus_verbose ("Freeing slot %d on allocator %p total %d allocated %d used\n", |
| *slot_id_p, allocator, allocator->n_allocated_slots, allocator->n_used_slots); |
| |
| allocator->allocated_slots[*slot_id_p].slot_id = -1; |
| *slot_id_p = -1; |
| |
| allocator->n_used_slots -= 1; |
| |
| if (allocator->n_used_slots == 0) |
| { |
| DBusMutex **mutex_loc = allocator->lock_loc; |
| |
| dbus_free (allocator->allocated_slots); |
| allocator->allocated_slots = NULL; |
| allocator->n_allocated_slots = 0; |
| allocator->lock_loc = NULL; |
| |
| _dbus_mutex_unlock (*mutex_loc); |
| } |
| else |
| { |
| _dbus_mutex_unlock (*(allocator->lock_loc)); |
| } |
| } |
| |
| /** |
| * Initializes a slot list. |
| * @param list the list to initialize. |
| */ |
| void |
| _dbus_data_slot_list_init (DBusDataSlotList *list) |
| { |
| list->slots = NULL; |
| list->n_slots = 0; |
| } |
| |
| /** |
| * Stores a pointer in the data slot list, along with an optional |
| * function to be used for freeing the data when the data is set |
| * again, or when the slot list is finalized. The slot number must |
| * have been allocated with _dbus_data_slot_allocator_alloc() for the |
| * same allocator passed in here. The same allocator has to be used |
| * with the slot list every time. |
| * |
| * @param allocator the allocator to use |
| * @param list the data slot list |
| * @param slot the slot number |
| * @param data the data to store |
| * @param free_data_func finalizer function for the data |
| * @param old_free_func free function for any previously-existing data |
| * @param old_data previously-existing data, should be freed with old_free_func |
| * @returns #TRUE if there was enough memory to store the data |
| */ |
| dbus_bool_t |
| _dbus_data_slot_list_set (DBusDataSlotAllocator *allocator, |
| DBusDataSlotList *list, |
| int slot, |
| void *data, |
| DBusFreeFunction free_data_func, |
| DBusFreeFunction *old_free_func, |
| void **old_data) |
| { |
| #ifndef DBUS_DISABLE_ASSERT |
| /* We need to take the allocator lock here, because the allocator could |
| * be e.g. realloc()ing allocated_slots. We avoid doing this if asserts |
| * are disabled, since then the asserts are empty. |
| */ |
| _dbus_mutex_lock (*(allocator->lock_loc)); |
| _dbus_assert (slot < allocator->n_allocated_slots); |
| _dbus_assert (allocator->allocated_slots[slot].slot_id == slot); |
| _dbus_mutex_unlock (*(allocator->lock_loc)); |
| #endif |
| |
| if (slot >= list->n_slots) |
| { |
| DBusDataSlot *tmp; |
| int i; |
| |
| tmp = dbus_realloc (list->slots, |
| sizeof (DBusDataSlot) * (slot + 1)); |
| if (tmp == NULL) |
| return FALSE; |
| |
| list->slots = tmp; |
| i = list->n_slots; |
| list->n_slots = slot + 1; |
| while (i < list->n_slots) |
| { |
| list->slots[i].data = NULL; |
| list->slots[i].free_data_func = NULL; |
| ++i; |
| } |
| } |
| |
| _dbus_assert (slot < list->n_slots); |
| |
| *old_data = list->slots[slot].data; |
| *old_free_func = list->slots[slot].free_data_func; |
| |
| list->slots[slot].data = data; |
| list->slots[slot].free_data_func = free_data_func; |
| |
| return TRUE; |
| } |
| |
| /** |
| * Retrieves data previously set with _dbus_data_slot_list_set_data(). |
| * The slot must still be allocated (must not have been freed). |
| * |
| * @param allocator the allocator slot was allocated from |
| * @param list the data slot list |
| * @param slot the slot to get data from |
| * @returns the data, or #NULL if not found |
| */ |
| void* |
| _dbus_data_slot_list_get (DBusDataSlotAllocator *allocator, |
| DBusDataSlotList *list, |
| int slot) |
| { |
| #ifndef DBUS_DISABLE_ASSERT |
| /* We need to take the allocator lock here, because the allocator could |
| * be e.g. realloc()ing allocated_slots. We avoid doing this if asserts |
| * are disabled, since then the asserts are empty. |
| */ |
| _dbus_mutex_lock (*(allocator->lock_loc)); |
| _dbus_assert (slot >= 0); |
| _dbus_assert (slot < allocator->n_allocated_slots); |
| _dbus_assert (allocator->allocated_slots[slot].slot_id == slot); |
| _dbus_mutex_unlock (*(allocator->lock_loc)); |
| #endif |
| |
| if (slot >= list->n_slots) |
| return NULL; |
| else |
| return list->slots[slot].data; |
| } |
| |
| /** |
| * Frees all data slots contained in the list, calling |
| * application-provided free functions if they exist. |
| * |
| * @param list the list to clear |
| */ |
| void |
| _dbus_data_slot_list_clear (DBusDataSlotList *list) |
| { |
| int i; |
| |
| i = 0; |
| while (i < list->n_slots) |
| { |
| if (list->slots[i].free_data_func) |
| (* list->slots[i].free_data_func) (list->slots[i].data); |
| list->slots[i].data = NULL; |
| list->slots[i].free_data_func = NULL; |
| ++i; |
| } |
| } |
| |
| /** |
| * Frees the data slot list and all data slots contained |
| * in it, calling application-provided free functions |
| * if they exist. |
| * |
| * @param list the list to free |
| */ |
| void |
| _dbus_data_slot_list_free (DBusDataSlotList *list) |
| { |
| _dbus_data_slot_list_clear (list); |
| |
| dbus_free (list->slots); |
| list->slots = NULL; |
| list->n_slots = 0; |
| } |
| |
| /** @} */ |
| |
| #ifdef DBUS_BUILD_TESTS |
| #include "dbus-test.h" |
| #include <stdio.h> |
| |
| static int free_counter; |
| |
| static void |
| test_free_slot_data_func (void *data) |
| { |
| int i = _DBUS_POINTER_TO_INT (data); |
| |
| _dbus_assert (free_counter == i); |
| ++free_counter; |
| } |
| |
| /** |
| * Test function for data slots |
| */ |
| dbus_bool_t |
| _dbus_data_slot_test (void) |
| { |
| DBusDataSlotAllocator allocator; |
| DBusDataSlotList list; |
| int i; |
| DBusFreeFunction old_free_func; |
| void *old_data; |
| DBusMutex *mutex; |
| |
| if (!_dbus_data_slot_allocator_init (&allocator)) |
| _dbus_assert_not_reached ("no memory for allocator"); |
| |
| _dbus_data_slot_list_init (&list); |
| |
| _dbus_mutex_new_at_location (&mutex); |
| if (mutex == NULL) |
| _dbus_assert_not_reached ("failed to alloc mutex"); |
| |
| #define N_SLOTS 100 |
| |
| i = 0; |
| while (i < N_SLOTS) |
| { |
| /* we don't really want apps to rely on this ordered |
| * allocation, but it simplifies things to rely on it |
| * here. |
| */ |
| dbus_int32_t tmp = -1; |
| |
| _dbus_data_slot_allocator_alloc (&allocator, &mutex, &tmp); |
| |
| if (tmp != i) |
| _dbus_assert_not_reached ("did not allocate slots in numeric order\n"); |
| |
| ++i; |
| } |
| |
| i = 0; |
| while (i < N_SLOTS) |
| { |
| if (!_dbus_data_slot_list_set (&allocator, &list, |
| i, |
| _DBUS_INT_TO_POINTER (i), |
| test_free_slot_data_func, |
| &old_free_func, &old_data)) |
| _dbus_assert_not_reached ("no memory to set data"); |
| |
| _dbus_assert (old_free_func == NULL); |
| _dbus_assert (old_data == NULL); |
| |
| _dbus_assert (_dbus_data_slot_list_get (&allocator, &list, i) == |
| _DBUS_INT_TO_POINTER (i)); |
| |
| ++i; |
| } |
| |
| free_counter = 0; |
| i = 0; |
| while (i < N_SLOTS) |
| { |
| if (!_dbus_data_slot_list_set (&allocator, &list, |
| i, |
| _DBUS_INT_TO_POINTER (i), |
| test_free_slot_data_func, |
| &old_free_func, &old_data)) |
| _dbus_assert_not_reached ("no memory to set data"); |
| |
| _dbus_assert (old_free_func == test_free_slot_data_func); |
| _dbus_assert (_DBUS_POINTER_TO_INT (old_data) == i); |
| |
| (* old_free_func) (old_data); |
| _dbus_assert (i == (free_counter - 1)); |
| |
| _dbus_assert (_dbus_data_slot_list_get (&allocator, &list, i) == |
| _DBUS_INT_TO_POINTER (i)); |
| |
| ++i; |
| } |
| |
| free_counter = 0; |
| _dbus_data_slot_list_free (&list); |
| |
| _dbus_assert (N_SLOTS == free_counter); |
| |
| i = 0; |
| while (i < N_SLOTS) |
| { |
| dbus_int32_t tmp = i; |
| |
| _dbus_data_slot_allocator_free (&allocator, &tmp); |
| _dbus_assert (tmp == -1); |
| ++i; |
| } |
| |
| _dbus_mutex_free_at_location (&mutex); |
| |
| return TRUE; |
| } |
| |
| #endif /* DBUS_BUILD_TESTS */ |