blob: dc4f19ab6f74eee58a539016e81a51d80f867638 [file] [log] [blame]
/******************************************************************************
*
* <:copyright-BRCM:2016:DUAL/GPL:standard
*
* Copyright (c) 2016 Broadcom
* All Rights Reserved
*
* Unless you and Broadcom execute a separate written software license
* agreement governing use of this software, this software is licensed
* to you under the terms of the GNU General Public License version 2
* (the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php,
* with the following added to such license:
*
* As a special exception, the copyright holders of this software give
* you permission to link this software with independent modules, and
* to copy and distribute the resulting executable under terms of your
* choice, provided that you also meet, for each linked independent
* module, the terms and conditions of the license of that module.
* An independent module is a module which is not derived from this
* software. The special exception does not apply to any modifications
* of the software.
*
* Not withstanding the above, under no circumstances may you combine
* this software in any way with any other Broadcom software provided
* under a license other than the GPL, without Broadcom's express prior
* written consent.
*
* :>
*
*****************************************************************************/
/*
* Data Base Engine
*
* Proprietary and confidential.
*/
#include <bcmos_system.h>
#include <bcm_db_engine.h>
/* DB trace level */
bcmos_trace_level bcmdb_trace_level = BCMOS_TRACE_LEVEL_ERROR;
#define BCMDB_TRACE(level, fmt, args...) \
do { \
if (level <= bcmdb_trace_level) \
bcmos_trace(level, "%s#%d> " fmt, __FUNCTION__, __LINE__, ## args); \
} while (0)
/* Print error trace conditionally, depending on the current trace level */
#define BCMDB_TRACE_ERROR(fmt, args...) \
BCMDB_TRACE(BCMOS_TRACE_LEVEL_ERROR, "DB ERR: %s#%d> " fmt, __FUNCTION__, __LINE__, ## args)
/* Print warning trace conditionally, depending on the current trace level */
#define BCMDB_TRACE_WARNING(fmt, args...) \
BCMDB_TRACE(BCMOS_TRACE_LEVEL_WARNING, "DB WARN: %s#%d> " fmt, __FUNCTION__, __LINE__, ## args)
/* Print info trace conditionally, depending on the current trace level */
#define BCMDB_TRACE_INFO(fmt, args...) \
BCMDB_TRACE(BCMOS_TRACE_LEVEL_INFO, "DB INFO: %s#%d> " fmt, __FUNCTION__, __LINE__, ## args)
/* Enable debug prints */
#define BCMDB_DEBUG
#ifdef BCMDB_DEBUG
/* Print debug trace conditionally, depending on the current trace level */
#define BCMDB_TRACE_DEBUG(fmt, args...) \
BCMDB_TRACE(BCMOS_TRACE_LEVEL_DEBUG, "DB DBG: %s#%d> " fmt, __FUNCTION__, __LINE__, ## args)
#else
#define BCMDB_TRACE_DEBUG(fmt, args...)
#endif
/** Data base entry
* \ingroup bcmdb
*/
struct bcmdb_entry
{
void *data; /* Set or record */
uint8_t flags;
#define BCMDB_FLAG_VALID 0x01 /**< Entry is valid */
#define BCMDB_FLAG_RECORD 0x02 /**< Record */
#define BCMDB_FLAG_SOS 0x10 /**< Set of sets */
uint8_t read_count;
uint8_t write_pending;
uint8_t ffu;
};
/** Set/Record change notification
* \ingroup bcmdb
*/
typedef struct bcmdb_notify
{
struct bcmdb_notify *next;
bcmdb_notify_cb cb;
long cb_priv;
} bcmdb_notify;
/** Data Base Set control block
* \ingroup bcmdb
*/
struct bcmdb_set
{
bcmdb_entry entry; /**< Common fields for set and record */
char *name; /**< Set name */
bcmdb_set *parent; /**< Set parent */
bcmdb_key my_key; /**< Key in the parent set */
int max_entries; /**< Max number of elements in the set. -1=inlimited */
int num_entries; /**< Current number of elements in the set */
int entry_size; /**< Set entry size. */
int magic; /**< Magic number */
#define BCMDB_MAGIC_ACTIVE_SET (('a'<<24) | ('s'<<16) | ('e'<<8) | 't')
#define BCMDB_MAGIC_FREE_SET (('f'<<24) | ('s'<<16) | ('e'<<8) | 't')
/** Get next element */
bcmdb_key (*entry_get_next)(const bcmdb_set *this, bcmdb_key cur);
/** Add new entry. returns 0 if ok */
int (*entry_new)(bcmdb_set *this, bcmdb_key key, const void *data);
/** Delete entry */
int (*entry_delete)(bcmdb_set *this, bcmdb_entry *entry);
/*
* Handle – key mapping
*/
/** Convert entry handle to entry key */
bcmdb_key (*handle_to_key)(const bcmdb_set *this, const bcmdb_entry *entry);
/** Convert entry key to entry handle */
bcmdb_entry *(*key_to_handle)(const bcmdb_set *this, bcmdb_key key);
/*
* Set/Record locking
* entry is handle of the set or record to be locked/unlocked
*/
/** Lock the whole set for reading */
void (*lock_set_read)(bcmdb_set *set);
/** Unlock set locked for reading */
void (*unlock_set_read)(bcmdb_set *set);
/** Lock the whole set for update */
long (*lock_set_modify)(bcmdb_set *set);
/** Unlock set locked for update */
void (*unlock_set_modify)(bcmdb_set *set, long fastlock_flags);
/** Lock the set recursively for update (SoS only) */
void (*lock_set_recursively_modify)(bcmdb_set *set);
/** Unlock recursively set locked for update (SoS only) */
void (*unlock_set_recursively_modify)(bcmdb_set *set);
/** Lock record for reading */
void *(*lock_record_read)(bcmdb_set *set, bcmdb_key key);
/** Release read lock */
void (*unlock_record_read)(bcmdb_set *set, bcmdb_key key);
/** Lock record for modification. */
void *(*lock_record_write)(bcmdb_set *set, bcmdb_key key, int is_deletion);
/** Release modify lock */
void (*unlock_record_write)(bcmdb_set *set, int is_deletion, int is_cancellation);
/** Format function that converts record data to human-readable form */
bcmdb_format_cb format;
/** Update notification mechanism.\n
* Note that notification functions are called before the actual update of the data base, so that
* there is an option to abort the update if needed.
*/
bcmdb_notify *notify_list_head;
/** Shadow data record */
void *shadow_data;
/** holds the pointer to a write-locked entry */
bcmdb_entry *write_locked_entry ;
/** mutex that prevents multiple simultaneous updates */
bcmos_mutex mutex_update;
/** semaphore that holds update while there are unfinished reads */
bcmos_sem sem_wait_read_to_finish;
/** fastlock */
bcmos_fastlock fastlock;
};
/**< Data base record
* \ingroup bcmdb
*/
struct bcmdb_record
{
struct bcmdb_entry e; /**< Entry - common part for set and record */
};
/*
* DB backend callbacks
*/
/*
* Array-based backend
*/
/** Get next element */
static bcmdb_key _bcmdb_array_entry_get_next(const bcmdb_set *this, bcmdb_key key)
{
BUG_ON(!this->entry.data);
if (key < 0)
key = 0;
else
++key;
while((unsigned)key<this->max_entries)
{
bcmdb_entry *entry = (bcmdb_entry *)this->entry.data + key;
if ((entry->flags & BCMDB_FLAG_VALID))
break;
++key;
}
if ((unsigned)key >= this->max_entries)
return BCMDB_KEY_NO_MORE; /* no more */
return key;
}
/*
* Handle – key mapping
*/
/** Convert entry handle to entry key */
static inline bcmdb_key _bcmdb_array_handle_to_key(const bcmdb_set *this, const bcmdb_entry *entry)
{
bcmdb_key key;
BUG_ON(!this);
BUG_ON(!entry);
BUG_ON(!this->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!this->entry.data);
key = entry - (bcmdb_entry *)this->entry.data;
if ((unsigned)key >= this->max_entries)
return BCMDB_KEY_INVAL;
return key;
}
/** Convert entry key to entry handle */
static inline bcmdb_entry *_bcmdb_array_key_to_handle(const bcmdb_set *this, bcmdb_key key)
{
BUG_ON(!this);
BUG_ON(!this->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!this->entry.data);
if ((unsigned long)key >= this->max_entries)
return NULL;
return (bcmdb_entry *)this->entry.data + key;
}
/** sem-based set read-lock */
static void bcmdb_set_lock_read_sem(bcmdb_set *set)
{
bcmos_mutex_lock(&set->mutex_update, BCMOS_WAIT_FOREVER);
}
/** sem-based set read-unlock */
static void bcmdb_set_unlock_read_sem(bcmdb_set *set)
{
bcmos_mutex_unlock(&set->mutex_update);
}
/** sem-based set modify-lock */
static long bcmdb_set_lock_modify_sem(bcmdb_set *set)
{
bcmos_mutex_lock(&set->mutex_update, BCMOS_WAIT_FOREVER);
return 0;
}
/** sem-based set modify-unlock */
static void bcmdb_set_unlock_modify_sem(bcmdb_set *set, long fastlock_flags)
{
bcmos_mutex_unlock(&set->mutex_update);
}
/** helper function which recursively locks all subsets of SoS
* for modify */
static void bcmdb_recursive_subsets_lock_modify(bcmdb_set *sos)
{
int key;
int left_entries = sos->num_entries;
bcmdb_entry *entry;
bcmdb_set *subset;
BUG_ON(!(sos->entry.flags & BCMDB_FLAG_SOS));
for (key = 0; key < sos->max_entries; ++key)
{
entry = sos->key_to_handle(sos, key);
if ((entry->flags & BCMDB_FLAG_VALID))
{
subset = (bcmdb_set *)entry->data;
subset->lock_set_recursively_modify(subset);
--left_entries;
/* if we handled all subsets we can break the "for" */
if (left_entries==0)
{
break;
}
}
}
}
/** helper function which recursively unlocks all subsets of SoS
* for modify */
static void bcmdb_recursive_subsets_unlock_modify(bcmdb_set *sos)
{
int key;
int left_entries = sos->num_entries;
bcmdb_entry *entry;
bcmdb_set *subset;
BUG_ON(!(sos->entry.flags & BCMDB_FLAG_SOS));
for (key = 0; key < sos->max_entries; ++key)
{
entry = sos->key_to_handle(sos, key);
if ((entry->flags & BCMDB_FLAG_VALID))
{
subset = (bcmdb_set *)entry->data;
subset->unlock_set_recursively_modify(subset);
--left_entries;
/* if we handled all subsets we can break the "for" */
if (left_entries==0)
{
break;
}
}
}
}
/** sem-based set modify-lock recursively */
static void bcmdb_set_lock_recursively_modify_sem(bcmdb_set *set)
{
BCMDB_TRACE_DEBUG("lock set modify recursively: %s\n", set->name);
bcmos_mutex_lock(&set->mutex_update, BCMOS_WAIT_FOREVER);
BCMDB_TRACE_DEBUG("mutex was locked\n");
if ((set->entry.flags & BCMDB_FLAG_SOS))
{
BCMDB_TRACE_DEBUG("going to lock the subsets\n");
bcmdb_recursive_subsets_lock_modify(set);
}
}
/** sem-based set modify-unlock recursively */
static void bcmdb_set_unlock_recursively_modify_sem(bcmdb_set *set)
{
BCMDB_TRACE_DEBUG("unlock set modify recursively: %s\n", set->name);
if ((set->entry.flags & BCMDB_FLAG_SOS))
{
BCMDB_TRACE_DEBUG("going to unlock the subsets\n");
bcmdb_recursive_subsets_unlock_modify(set);
}
bcmos_mutex_unlock(&set->mutex_update);
BCMDB_TRACE_DEBUG("mutex was unlocked\n");
}
/** NB-read-sem-write policy: set read lock */
static void bcmdb_set_lock_read__nb_read_sem_write(bcmdb_set *set)
{
long flags;
BCMDB_TRACE_DEBUG("lock set read: %s\n", set->name);
flags = bcmos_fastlock_lock(&set->fastlock);
++set->entry.read_count;
BCMDB_TRACE_DEBUG("read_count is now: %u\n", set->entry.read_count);
bcmos_fastlock_unlock(&set->fastlock, flags);
}
/** NB-read-sem-write policy: set read unlock */
static void bcmdb_set_unlock_read__nb_read_sem_write(bcmdb_set *set)
{
long flags;
BCMDB_TRACE_DEBUG("unlock set read: %s\n", set->name);
flags = bcmos_fastlock_lock(&set->fastlock);
BUG_ON(!set->entry.read_count);
if (!(--set->entry.read_count) &&
set->entry.write_pending)
{
BCMDB_TRACE_DEBUG("going to wake pending writer\n");
set->entry.write_pending = 0;
bcmos_fastlock_unlock(&set->fastlock, flags);
bcmos_sem_post(&set->sem_wait_read_to_finish);
}
else
{
bcmos_fastlock_unlock(&set->fastlock, flags);
}
}
/** NB-read-sem-write policy: set modify lock */
static long bcmdb_set_lock_modify__nb_read_sem_write(bcmdb_set *set)
{
long flags;
BCMDB_TRACE_DEBUG("lock set modify: %s\n", set->name);
bcmos_mutex_lock(&set->mutex_update, BCMOS_WAIT_FOREVER);
BCMDB_TRACE_DEBUG("mutex was locked\n");
while(1)
{
flags = bcmos_fastlock_lock(&set->fastlock);
if (!set->entry.read_count)
break;
/* Wait until read is completed */
set->entry.write_pending = 1;
bcmos_fastlock_unlock(&set->fastlock, flags);
bcmos_sem_wait(&set->sem_wait_read_to_finish, BCMOS_WAIT_FOREVER);
}
/* At this point fastlock is taken and there are no pending reads */
BCMDB_TRACE_DEBUG("fastlock is taken, no active reads\n");
return flags;
}
/** NB-read-sem-write policy: set modify unlock */
static void bcmdb_set_unlock_modify__nb_read_sem_write(bcmdb_set *set, long fastlock_flags)
{
BCMDB_TRACE_DEBUG("unlock set modify: %s\n", set->name);
bcmos_fastlock_unlock(&set->fastlock, fastlock_flags);
bcmos_mutex_unlock(&set->mutex_update);
BCMDB_TRACE_DEBUG("mutex was unlocked\n");
}
/** sem-read/sem-write policy: lock entry */
static void *bcmdb_sem_read_sem_write_lock(bcmdb_set *set, bcmdb_key key, int is_deletion)
{
bcmdb_entry *entry;
bcmos_mutex_lock(&set->mutex_update, BCMOS_WAIT_FOREVER);
if (is_deletion)
{
/* there is nothing to return in deletion case */
return NULL;
}
entry = set->key_to_handle(set, key);
if (!entry || !(entry->flags & BCMDB_FLAG_VALID))
{
bcmos_mutex_unlock(&set->mutex_update);
return NULL;
}
return entry->data;
}
/** sem-read/sem-write policy: unlock entry */
static void bcmdb_sem_read_sem_write_unlock(bcmdb_set *set)
{
bcmos_mutex_unlock(&set->mutex_update);
}
/** sem-read/sem-write policy: lock entry for reading */
static void *bcmdb_sem_read_sem_write_lock_read(bcmdb_set *set, bcmdb_key key)
{
return bcmdb_sem_read_sem_write_lock(set, key, 0) ;
}
/** sem-read/sem-write policy: unlock entry for reading */
static void bcmdb_sem_read_sem_write_unlock_read(bcmdb_set *set, bcmdb_key key)
{
bcmdb_sem_read_sem_write_unlock(set);
}
/** sem-read/sem-write policy: lock entry for writing */
static void *bcmdb_sem_read_sem_write_lock_write(bcmdb_set *set, bcmdb_key key, int is_deletion)
{
return bcmdb_sem_read_sem_write_lock(set, key, is_deletion) ;
}
/** sem-read/sem-write policy: unlock entry for writing */
static void bcmdb_sem_read_sem_write_unlock_write(bcmdb_set *set, int is_deletion, int is_cancellation)
{
bcmdb_sem_read_sem_write_unlock(set);
}
/** non-blocking-read/shadow write policy: Lock entry for reading */
static void *bcmdb_nb_read_shadow_write_lock_read(bcmdb_set *set, bcmdb_key key)
{
bcmdb_entry *entry;
long flags;
flags = bcmos_fastlock_lock(&set->fastlock);
entry = set->key_to_handle(set, key);
if (!entry || !(entry->flags & BCMDB_FLAG_VALID))
{
bcmos_fastlock_unlock(&set->fastlock, flags);
return NULL;
}
++entry->read_count;
BCMDB_TRACE_DEBUG("lock record read: %s, key=%d new_read_count=%d\n", set->name, key, entry->read_count);
bcmos_fastlock_unlock(&set->fastlock, flags);
return entry->data;
}
/** non-blocking-read/shadow write policy: Unlock entry for reading */
static void bcmdb_nb_read_shadow_write_unlock_read(bcmdb_set *set, bcmdb_key key)
{
bcmdb_entry *entry=set->key_to_handle(set, key);
long flags;
BUG_ON(!entry);
BCMDB_TRACE_DEBUG("unlock record read: %s, key=%d\n", set->name, key);
flags = bcmos_fastlock_lock(&set->fastlock);
/* If write is pending - finish it and release write lock */
BUG_ON(!entry->read_count);
if (!(--entry->read_count) && set->entry.write_pending)
{
BCMDB_TRACE_DEBUG("going to wake pending writer\n");
/* Write was pending. Release write task */
set->entry.write_pending = 0;
bcmos_fastlock_unlock(&set->fastlock, flags);
bcmos_sem_post(&set->sem_wait_read_to_finish);
}
else
{
BCMDB_TRACE_DEBUG("read_count is now: %u\n", entry->read_count);
bcmos_fastlock_unlock(&set->fastlock, flags);
}
}
/** non-blocking-read/shadow write policy: Lock entry for
* writing/deletion.
* returned value of NULL means error only in case that
* is_deletion is 0 */
static void *bcmdb_nb_read_shadow_write_lock_write(bcmdb_set *set, bcmdb_key key, int is_deletion)
{
bcmdb_entry *entry;
BCMDB_TRACE_DEBUG("lock record write: %s, key=%d\n", set->name, key);
bcmos_mutex_lock(&set->mutex_update, BCMOS_WAIT_FOREVER);
BCMDB_TRACE_DEBUG("mutex was locked\n");
if (is_deletion)
{
/* there is nothing to return in deletion case */
return NULL;
}
/* this check is needed since mutex_update is task-aware.
it is not allowed for a task to lock for writing a 2nd record before unlocking the first one. */
if (set->write_locked_entry)
{
BCMDB_TRACE_ERROR("there is already an entry locked for writing\n");
bcmos_mutex_unlock(&set->mutex_update);
return NULL;
}
entry = set->key_to_handle(set, key);
if (!entry || !(entry->flags & BCMDB_FLAG_VALID))
{
bcmos_mutex_unlock(&set->mutex_update);
return NULL;
}
/* Copy data to shadow entry */
memcpy(set->shadow_data, entry->data, set->entry_size);
set->write_locked_entry = entry;
return set->shadow_data;
}
/** non-blocking-read/shadow write policy: Unlock entry for
* writing/deletion */
static void bcmdb_nb_read_shadow_write_unlock_write(bcmdb_set *set, int is_deletion, int is_cancellation)
{
bcmdb_entry *entry = set->write_locked_entry;
long flags;
void *old_data;
/* no entry is locked */
BUG_ON(!entry);
BCMDB_TRACE_DEBUG("unlock record write: %s\n", set->name);
/* cancellation: no need to update the entry from the shadow (or to delete the entry in case of deletion). */
if (is_cancellation)
{
set->write_locked_entry = NULL;
bcmos_mutex_unlock(&set->mutex_update);
return;
}
while(1)
{
flags = bcmos_fastlock_lock(&set->fastlock);
/* Wait until neither record nor set are locked for reading */
if (!entry->read_count && !set->entry.read_count)
break;
/* Read lock is active. wait */
BCMDB_TRACE_DEBUG("read lock is active. going to sleep.\n");
set->entry.write_pending = 1;
bcmos_fastlock_unlock(&set->fastlock, flags);
bcmos_sem_wait(&set->sem_wait_read_to_finish, BCMOS_WAIT_FOREVER);
}
/* At this point there is no read lock and fastlock is taken. */
BCMDB_TRACE_DEBUG("fastlock is taken, no active reads\n");
if (is_deletion)
{
/* delete the entry */
set->entry_delete(set, entry);
}
else
{
/* Exchange record data with shadow and release all locks. */
old_data = entry->data;
entry->data = set->shadow_data;
set->shadow_data = old_data;
set->write_locked_entry = NULL;
}
bcmos_fastlock_unlock(&set->fastlock, flags);
bcmos_mutex_unlock(&set->mutex_update);
BCMDB_TRACE_DEBUG("mutex was unlocked\n");
}
/** none policy: set read-lock */
static inline void bcmdb_set_lock_read_dummy(bcmdb_set *set)
{
}
/** none policy: set read-unlock */
static inline void bcmdb_set_unlock_read_dummy(bcmdb_set *set)
{
}
/** none policy: set modify-lock */
static inline long bcmdb_set_lock_modify_dummy(bcmdb_set *set)
{
return 0;
}
/** none policy: set modify-unlock */
static inline void bcmdb_set_unlock_modify_dummy(bcmdb_set *set, long fastlock_flags)
{
}
/** none policy: set modify-lock recursively */
static void bcmdb_set_lock_recursively_modify_dummy(bcmdb_set *set)
{
}
/** none policy: set modify-unlock recursively */
static void bcmdb_set_unlock_recursively_modify_dummy(bcmdb_set *set)
{
}
/** none policy: record lock */
static inline void *bcmdb_dummy_lock(bcmdb_set *set, bcmdb_key key, int is_deletion)
{
bcmdb_entry *entry;
/* there is nothing to return in deletion case */
if (is_deletion)
return NULL;
entry = set->key_to_handle(set, key);
if (!entry || !(entry->flags & BCMDB_FLAG_VALID))
return NULL;
return entry->data;
}
/** none policy: record unlock */
static inline void bcmdb_dummy_unlock(bcmdb_set *set)
{
}
/** none policy: record lock for reading */
static inline void *bcmdb_dummy_lock_read(bcmdb_set *set, bcmdb_key key)
{
return bcmdb_dummy_lock(set, key, 0);
}
/** none policy: record unlock for reading */
static inline void bcmdb_dummy_unlock_read(bcmdb_set *set, bcmdb_key key)
{
bcmdb_dummy_unlock(set);
}
/** none policy: record lock for writing */
static inline void *bcmdb_dummy_lock_write(bcmdb_set *set, bcmdb_key key, int is_deletion)
{
return bcmdb_dummy_lock(set, key, is_deletion);
}
/** none policy: record unlock for writing */
static inline void bcmdb_dummy_unlock_write(bcmdb_set *set, int is_deletion, int is_cancellation)
{
bcmdb_dummy_unlock(set);
}
/** Add new sub-set. returns 0 if ok
* data contains new set handle
*/
static int bcmdb_set_new(bcmdb_set *this, bcmdb_key key, const void *data)
{
/* Although this callback takes "const void *" parameter,
* it is just for compatibility fith SoR->new_entry interface.
* For SoS this parameter is not constant on application level
* (see bcmdb_set_add())
*/
bcmdb_entry *entry = this->key_to_handle(this, key);
bcmdb_set *new_set = (bcmdb_set *)(long)data;
if (!entry)
return BCM_ERR_PARM;
if ((entry->flags & BCMDB_FLAG_VALID))
return BCM_ERR_ALREADY;
++this->num_entries;
entry->data = (void *)(long)data;
entry->flags |= BCMDB_FLAG_VALID;
new_set->my_key = key;
new_set->parent = this;
return 0;
}
/** Add new record. returns 0 if ok
* data contains record data pointer
*/
static int bcmdb_record_new(bcmdb_set *this, bcmdb_key key, const void *data)
{
bcmdb_record *record = (bcmdb_record *)this->key_to_handle(this, key);
if (!record || !record->e.data)
return BCM_ERR_PARM;
if ((record->e.flags & BCMDB_FLAG_VALID))
return BCM_ERR_ALREADY;
++this->num_entries;
memcpy(record->e.data, data, this->entry_size);
record->e.flags |= BCMDB_FLAG_VALID;
return 0;
}
/** Delete entry */
static int bcmdb_entry_delete(bcmdb_set *this, bcmdb_entry *entry)
{
if (!entry)
return BCM_ERR_PARM;
if (!(entry->flags & BCMDB_FLAG_VALID))
return BCM_ERR_ALREADY;
entry->flags &= ~BCMDB_FLAG_VALID;
--this->num_entries;
return 0;
}
/*
* External APIs
*/
/** Initialize data base engine
*
* \return
* 0 - OK\n
* <0 - error code
* \ingroup bcmdb
*/
int bcmdb_module_init(void)
{
return 0;
}
/** Make set-of-sets control block.
*
* Helper function that creates a set of sets with reasonable defaults for all callbacks and fields.
* Once created, the set control block can be tuned before adding the new set to its parent set.
* \param[in] init set parameters
* \param[out] new_set set control block
* \return
* 0 - OK\n
* <0 - error code
*/
int bcmdb_make_set_of_sets(const bcmdb_sos_init *init, bcmdb_set **new_set)
{
bcmdb_set *sos;
bcmdb_entry *entries;
int rc;
/* Parameter check */
if (!init || !init->name || !new_set)
return BCM_ERR_PARM;
if ((init->backend_type == BCMDB_BACKEND_ARRAY) && !init->max_entries)
return BCM_ERR_PARM;
/* Allocate set control block and set records */
sos = bcmos_calloc(sizeof(bcmdb_set) + strlen(init->name) + 1);
if (!sos)
return BCM_ERR_NOMEM;
sos->name = (char *)(sos + 1);
strcpy(sos->name, init->name);
sos->entry_size = sizeof(bcmdb_set);
sos->max_entries = init->max_entries;
sos->my_key = BCMDB_KEY_INVAL;
sos->entry.flags = BCMDB_FLAG_SOS;
sos->magic = BCMDB_MAGIC_ACTIVE_SET;
/* Set backend callbacks */
switch(init->backend_type)
{
case BCMDB_BACKEND_ARRAY:
entries = bcmos_calloc(sizeof(bcmdb_set)*init->max_entries);
if (!entries)
{
bcmos_free(sos);
return BCM_ERR_NOMEM;
}
sos->entry.data = entries;
sos->entry_get_next = _bcmdb_array_entry_get_next;
sos->handle_to_key = _bcmdb_array_handle_to_key;
sos->key_to_handle = _bcmdb_array_key_to_handle;
sos->entry_new = bcmdb_set_new;
sos->entry_delete = bcmdb_entry_delete;
break;
default:
printf("Only array-based DB backend is supported\n");
bcmos_free(sos);
return BCM_ERR_NOT_SUPPORTED;
}
/* Set locking callbacks. SoS locking policy is always SEMAPHORE */
/* in SoS, locking for read is same as for write (and is done recursively). */
sos->lock_set_read = bcmdb_set_lock_recursively_modify_sem;
sos->unlock_set_read = bcmdb_set_unlock_recursively_modify_sem;
sos->lock_set_modify = bcmdb_set_lock_modify_sem;
sos->unlock_set_modify = bcmdb_set_unlock_modify_sem;
sos->lock_set_recursively_modify = bcmdb_set_lock_recursively_modify_sem ;
sos->unlock_set_recursively_modify = bcmdb_set_unlock_recursively_modify_sem ;
/* create mutex_update */
rc = bcmos_mutex_create(&sos->mutex_update, init->os_flags);
if (rc)
{
bcmos_free(entries);
bcmos_free(sos);
return BCM_ERR_NOMEM;
}
bcmos_fastlock_init(&sos->fastlock, init->os_flags);
*new_set = sos;
return 0;
}
/** Make set-of-records control block.
*
* Helper function that creates a set of records with reasonable defaults for all callbacks and fields.
* Once created, the set control block can be tuned before adding the new set to its parent set.
* \param[in] init set parameters
* \param[in] alloc_records true (1) - allocate memory for all records.
* \param[out] new_set set control block
* \return
* 0 - OK\n
* <0 - error code
*/
int bcmdb_make_set_of_records(const bcmdb_sor_init *init, int alloc_records, bcmdb_set **new_set)
{
bcmdb_set *sor;
bcmdb_entry *entries = NULL;
void *data = NULL;
int i;
int rc ;
/* Parameter check */
if (!init || !init->name)
return BCM_ERR_PARM;
if ((init->backend_type == BCMDB_BACKEND_ARRAY) && !init->max_entries)
return BCM_ERR_PARM;
if (!init->record_size)
return BCM_ERR_PARM;
/* Allocate set control block and set records */
sor = bcmos_calloc(sizeof(bcmdb_set) + strlen(init->name) + 1);
if (!sor)
return BCM_ERR_NOMEM;
sor->name = (char *)(sor + 1);
strcpy(sor->name, init->name);
sor->entry_size = init->record_size;
sor->max_entries = init->max_entries;
sor->my_key = BCMDB_KEY_INVAL;
sor->magic = BCMDB_MAGIC_ACTIVE_SET;
sor->format = init->format;
/* Set backend callbacks */
switch(init->backend_type)
{
case BCMDB_BACKEND_ARRAY:
entries = bcmos_calloc(sizeof(bcmdb_entry)*init->max_entries);
if (!entries)
{
bcmos_free(sor);
return BCM_ERR_NOMEM;
}
sor->entry.data = entries;
sor->entry_get_next = _bcmdb_array_entry_get_next;
sor->handle_to_key = _bcmdb_array_handle_to_key;
sor->key_to_handle = _bcmdb_array_key_to_handle;
sor->entry_new = bcmdb_record_new;
sor->entry_delete = bcmdb_entry_delete;
/* Preallocate data */
if (alloc_records)
{
int size = init->max_entries * init->record_size;
if (init->lock_policy == BCMDB_LOCK_NB_READ_SHADOW_WRITE)
size += init->record_size; /* room for shadow entry */
/* Allocate data + 1 extra for shadow area */
data = bcmos_calloc(size);
if (!data)
{
bcmos_free(entries);
bcmos_free(sor);
return BCM_ERR_NOMEM;
}
for(i=0; i<init->max_entries; i++)
{
bcmdb_entry *entry = (bcmdb_entry *)sor->entry.data + i;
entry->data = (void *)((long)data + i * init->record_size);
}
if (init->lock_policy == BCMDB_LOCK_NB_READ_SHADOW_WRITE)
{
sor->shadow_data = (void *)((long)data + i * init->record_size);
}
}
/* Initialize records */
for(i=0; i<init->max_entries; i++)
{
bcmdb_entry *entry = (bcmdb_entry *)sor->entry.data + i;
entry->flags = BCMDB_FLAG_RECORD;
}
break;
default:
printf("Only array-based DB backend is supported\n");
bcmos_free(sor);
return BCM_ERR_NOT_SUPPORTED;
}
/* Set locking callbacks based on locking policy */
switch(init->lock_policy)
{
case BCMDB_LOCK_SEM_READ_SEM_WRITE:
sor->lock_record_write = bcmdb_sem_read_sem_write_lock_write;
sor->lock_record_read = bcmdb_sem_read_sem_write_lock_read;
sor->unlock_record_write = bcmdb_sem_read_sem_write_unlock_write;
sor->unlock_record_read = bcmdb_sem_read_sem_write_unlock_read;
sor->lock_set_read = bcmdb_set_lock_read_sem;
sor->unlock_set_read = bcmdb_set_unlock_read_sem;
sor->lock_set_modify = bcmdb_set_lock_modify_sem;
sor->unlock_set_modify = bcmdb_set_unlock_modify_sem;
sor->lock_set_recursively_modify = bcmdb_set_lock_recursively_modify_sem ;
sor->unlock_set_recursively_modify = bcmdb_set_unlock_recursively_modify_sem ;
break;
case BCMDB_LOCK_NONE:
case BCMDB_LOCK_OTHER:
sor->lock_record_write = bcmdb_dummy_lock_write;
sor->lock_record_read = bcmdb_dummy_lock_read;
sor->unlock_record_write = bcmdb_dummy_unlock_write;
sor->unlock_record_read = bcmdb_dummy_unlock_read;
sor->lock_set_read = bcmdb_set_lock_read_dummy;
sor->unlock_set_read = bcmdb_set_unlock_read_dummy;
sor->lock_set_modify = bcmdb_set_lock_modify_dummy;
sor->unlock_set_modify = bcmdb_set_unlock_modify_dummy;
sor->lock_set_recursively_modify = bcmdb_set_lock_recursively_modify_dummy ;
sor->unlock_set_recursively_modify = bcmdb_set_unlock_recursively_modify_dummy ;
break;
case BCMDB_LOCK_NB_READ_SHADOW_WRITE:
sor->lock_record_write = bcmdb_nb_read_shadow_write_lock_write;
sor->lock_record_read = bcmdb_nb_read_shadow_write_lock_read;
sor->unlock_record_write = bcmdb_nb_read_shadow_write_unlock_write;
sor->unlock_record_read = bcmdb_nb_read_shadow_write_unlock_read;
sor->lock_set_read = bcmdb_set_lock_read__nb_read_sem_write;
sor->unlock_set_read = bcmdb_set_unlock_read__nb_read_sem_write;
sor->lock_set_modify = bcmdb_set_lock_modify__nb_read_sem_write;
sor->unlock_set_modify = bcmdb_set_unlock_modify__nb_read_sem_write;
sor->lock_set_recursively_modify = bcmdb_set_lock_recursively_modify_sem ;
sor->unlock_set_recursively_modify = bcmdb_set_unlock_recursively_modify_sem ;
break;
default:
printf("Lock policy %d is not supported\n", init->lock_policy);
if (data)
bcmos_free(data);
if (entries)
bcmos_free(entries);
bcmos_free(sor);
return BCM_ERR_NOT_SUPPORTED;
}
/* create mutex_update */
rc = bcmos_mutex_create(&sor->mutex_update, init->os_flags);
if (rc)
{
if (data)
bcmos_free(data);
if (entries)
bcmos_free(entries);
bcmos_free(sor);
return BCM_ERR_NOMEM;
}
/* create sem_wait_read_to_finish. it is initialized to be taken */
rc = bcmos_sem_create(&sor->sem_wait_read_to_finish, 0, init->os_flags);
if (rc)
{
/* no point to check here the error code of bcmos_mutex_destroy */
bcmos_mutex_destroy(&sor->mutex_update);
if (data)
bcmos_free(data);
if (entries)
bcmos_free(entries);
bcmos_free(sor);
return BCM_ERR_NOMEM;
}
bcmos_fastlock_init(&sor->fastlock, init->os_flags);
*new_set = sor;
return 0;
}
/** Lock data set for reading. When set is locked - it can't be
* modified.
*
* \param[in] set data base set to be locked
*
* \ingroup bcmdb
*/
void bcmdb_set_lock_read(bcmdb_set *set)
{
BUG_ON(!set);
BUG_ON(!set->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!set->lock_set_read);
set->lock_set_read(set);
}
/** Release data set lock
*
* Unlock set locked by \ref bcmdb_set_lock_read
*
* \param[in] set data base set to be unlocked
*
* \ingroup bcmdb
*/
void bcmdb_set_unlock_read(bcmdb_set *set)
{
BUG_ON(!set);
BUG_ON(!set->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!set->unlock_set_read);
set->unlock_set_read(set);
}
/** Lock data set for modify. If the set is SoS, the locking
* will be recursive.
*
* \param[in] set data base set to be locked
*
* \ingroup bcmdb
*/
void bcmdb_set_lock_modify(bcmdb_set *set)
{
BUG_ON(!set);
BUG_ON(!set->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!set->lock_set_recursively_modify);
set->lock_set_recursively_modify(set);
}
/** Release data set lock
*
* Unlock set locked by \ref bcmdb_set_lock_modify
*
* \param[in] set data base set to be unlocked
*
* \ingroup bcmdb
*/
void bcmdb_set_unlock_modify(bcmdb_set *set)
{
BUG_ON(!set);
BUG_ON(!set->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!set->unlock_set_recursively_modify);
set->unlock_set_recursively_modify(set);
}
/** Add set to the parent set with specific key.
*
* The function adds set to the parent set creating data base hierarchy.
* The function automatically acquires modify lock and releases it
* in the end of operation.
* \param[in] sos parent set of sets
* \param[in] key key to add new set at
* \param[in] new_set set control block
* \return
* =0 - OK\n
* <0 - error code
* \ingroup bcmdb
*/
int bcmdb_set_add(bcmdb_set *sos, bcmdb_key key, bcmdb_set *new_set)
{
int rc;
long fastlock_flags;
BUG_ON(!sos);
BUG_ON(!sos->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!(sos->entry.flags & BCMDB_FLAG_SOS));
BUG_ON(!new_set);
BUG_ON(!new_set->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!new_set->my_key == BCMDB_KEY_INVAL);
fastlock_flags = sos->lock_set_modify(sos);
rc = sos->entry_new(sos, key, new_set);
sos->unlock_set_modify(sos, fastlock_flags);
return rc;
}
/** Get set handle given its key.
*
* \param[in] sos parent set of sets
* \param[in] key set key.
* \return
* !=0 - set handle
* NULL- doesn't exist
* \ingroup bcmdb
*/
bcmdb_set *bcmdb_set_handle(const bcmdb_set *sos, bcmdb_key key)
{
bcmdb_entry *entry;
BUG_ON(!sos);
BUG_ON(!sos->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!(sos->entry.flags & BCMDB_FLAG_SOS));
entry = sos->key_to_handle(sos, key);
if (!entry || !(entry->flags & BCMDB_FLAG_VALID))
return NULL;
return (bcmdb_set *)entry->data;
}
/** Get set key given its handle.
*
* \param[in] set set handle
* \param[in] key set key.
* \return
* !=BCMDB_KEY_INVAL - set key
* BCMDB_KEY_INVAL - error
* \ingroup bcmdb
*/
bcmdb_key bcmdb_set_key(const bcmdb_set *set)
{
BUG_ON(!set);
BUG_ON(!set->magic == BCMDB_MAGIC_ACTIVE_SET);
return set->my_key;
}
/** Get set name
*
* \param[in] set set handle
* \return set name
* \ingroup bcmdb
*/
const char *bcmdb_set_name(const bcmdb_set *set)
{
BUG_ON(!set);
BUG_ON(!set->magic == BCMDB_MAGIC_ACTIVE_SET);
return set->name;
}
/** Get number of records in the set.
*
* \param[in] set set handle
* \return number of active records in the set
* \ingroup bcmdb
*/
int bcmdb_set_num_records(const bcmdb_set *set)
{
BUG_ON(!set);
BUG_ON(!set->magic == BCMDB_MAGIC_ACTIVE_SET);
return set->num_entries;
}
/** Get entry size
*
* \param[in] set set handle
* \return set entry size
* \ingroup bcmdb
*/
int bcmdb_set_entry_size(const bcmdb_set *set)
{
BUG_ON(!set);
BUG_ON(!set->magic == BCMDB_MAGIC_ACTIVE_SET);
return set->entry_size;
}
/** Add record to the parent set with specific key.
*
* The function creates a new record and adds it to the parent set with specific key.
* The function automatically acquires modify lock and releases it
* in the end of operation.
* \param[in] sor parent set of records
* \param[in] key key to add new set at
* \param[in] data record data. Data size is defined at parent SOR registration time.
* \param[out] p_record new record handle
* \return
* =0 - OK\n
* <0 - error code
* \ingroup bcmdb
*/
int bcmdb_record_add(bcmdb_set *sor, bcmdb_key key, const void *data)
{
int rc;
long fastlock_flags;
BUG_ON(!sor);
BUG_ON(!data);
BUG_ON(!sor->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!(sor->entry.flags & BCMDB_FLAG_SOS)==0);
fastlock_flags = sor->lock_set_modify(sor);
rc=sor->entry_new(sor, key, data);
sor->unlock_set_modify(sor, fastlock_flags);
return rc;
}
/** Delete record from the parent SoR given the record key.
*
* The function automatically acquires modify lock and releases it
* in the end of operation.
*
* \param[in] sor parent set of records
* \param[in] key record key.
*/
void bcmdb_record_delete(bcmdb_set *sor, bcmdb_key key)
{
BUG_ON(!sor);
BUG_ON(!sor->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!(sor->entry.flags & BCMDB_FLAG_SOS)==0);
sor->lock_record_write(sor, key, 1);
sor->unlock_record_write(sor, 1, 0);
}
/** Get record data pointer without locking.
*
* The function returns pointer to data structure stored in data base record.\n
* Attention! The caller is required to aquire read or write lock - as appropriate
* before calling this function and release the lock when processing is finished.
*
* \param[in] sor parent set of records
* \param[in] key record key
* \return
* data pointer. NULL if there is no record matching the key.
* \ingroup bcmdb
*/
void *bcmdb_record_getraw_nolock(bcmdb_set *sor, bcmdb_key key)
{
bcmdb_entry *entry;
BUG_ON(!sor);
BUG_ON(!sor->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!(sor->entry.flags & BCMDB_FLAG_SOS)==0);
entry = sor->key_to_handle(sor, key);
if (!entry || !(entry->flags & BCMDB_FLAG_VALID))
return NULL;
return entry->data;
}
/** Lock record for reading and return record data pointer.
*
* The function aquires read-lock and returns pointer to data structure stored in data base record.\n
* read-lock must be released separately when the pointer is no longer in use.
* Note that the default record-read lock is non-blocking and counting.
* That means that multiple processes cam read-lock the same record without blocking.
* The function is low-level. It is recommended to use macro \ref bcmdb_record_get_read instead.
*
* \param[in] sor parent set of records
* \param[in] key record key
* \return
* data pointer. NULL if there is no record matching the key.
* \ingroup bcmdb
*/
const void *bcmdb_record_getraw_read(bcmdb_set *sor, bcmdb_key key)
{
BUG_ON(!sor);
BUG_ON(!sor->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!(sor->entry.flags & BCMDB_FLAG_SOS)==0);
return sor->lock_record_read(sor, key);
}
/** Unlock record locked for reading.
*
* \param[in] sor parent set of records
* \param[in] key record key
*
* \ingroup bcmdb
*/
void bcmdb_record_unlock_read(bcmdb_set *sor, bcmdb_key key)
{
BUG_ON(!sor);
BUG_ON(!sor->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!(sor->entry.flags & BCMDB_FLAG_SOS)==0);
sor->unlock_record_read(sor, key);
}
/** Read record data into user area.
*
* The function aquires read-lock, reads data into user area and releases read-lock.
*
* \param[in] sor parent set of records
* \param[in] key record key
* \param[in] offset offset in data record
* \param[in] size data size. Note that offset+size must be <= record_size
* \param[in] data data pointer.
* \return
* =0-OK\n
* <0-error code
* \ingroup bcmdb
*/
int bcmdb_record_read(bcmdb_set *sor, bcmdb_key key, int offset, int size, void *data)
{
const void *d = bcmdb_record_getraw_read(sor, key);
if (!d)
return BCM_ERR_PARM;
if ((unsigned)offset + (unsigned)size > sor->entry_size)
{
bcmdb_record_unlock_read(sor, key);
return BCM_ERR_PARM;
}
memcpy(data, (const char *)d+(unsigned)offset, (unsigned)size);
bcmdb_record_unlock_read(sor, key);
return 0;
}
/** Lock record for writing and return record data pointer.
*
* The function aquires write-lock and returns pointer to data structure stored in data base record.\n
* write-lock must be released separately when the pointer is no longer in use.
* The function is low-level. It is recommended to use macro \ref bcmdb_record_get_write instead.
*
* \param[in] sor parent set of records
* \param[in] key record key
* \return
* data pointer. NULL if there is no record matching the key.
* \ingroup bcmdb
*/
void *bcmdb_record_getraw_write(bcmdb_set *sor, bcmdb_key key)
{
BUG_ON(!sor);
BUG_ON(!sor->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!(sor->entry.flags & BCMDB_FLAG_SOS)==0);
return sor->lock_record_write(sor, key, 0);
}
/** Unlock record locked for writing.
*
* \param[in] sor parent set of records
* \param[in] is_cancellation TRUE=cancel transaction
*
* \ingroup bcmdb
*/
void bcmdb_record_unlock_write(bcmdb_set *sor, int is_cancellation)
{
BUG_ON(!sor);
BUG_ON(!sor->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!(sor->entry.flags & BCMDB_FLAG_SOS)==0);
sor->unlock_record_write(sor, 0, is_cancellation);
}
/** Write record data.
*
* The function aquires modify-lock, replaces data stored in data base record
* and releses the lock.
*
* \param[in] sor parent set of records
* \param[in] key record key
* \param[in] offset offset in data record
* \param[in] size data size. Note that offset+size must be <= record_size
* \param[in] data data pointer.
* \return
* =0-OK\n
* <0-error code
* \ingroup bcmdb
*/
int bcmdb_record_write(bcmdb_set *sor, bcmdb_key key, int offset, int size, const void *data)
{
void *d=bcmdb_record_getraw_write(sor, key);
if (!d)
return BCM_ERR_PARM;
if ((unsigned)offset + (unsigned)size > sor->entry_size)
{
bcmdb_record_unlock_write(sor, 0);
return BCM_ERR_PARM;
}
memcpy((char *)d+(unsigned)offset, data, (unsigned)size);
bcmdb_record_unlock_write(sor, 0);
return 0;
}
/** Register notification function to get informed
* when data base set is modified.
*
* \param[in] sor parent set of records
* \param[in] cb callback function pointer
* \param[in] cb_priv private data that should be passed to the callback
* \return
* =0 - OK\n
* <0 - error code
* \ingroup bcmdb
*/
int bcmdb_set_notify_register(bcmdb_set *sor, bcmdb_notify_cb cb, long cb_priv)
{
bcmdb_notify *nf_new, *nf, *nf_prev = NULL;
BUG_ON(!sor);
BUG_ON(!sor->magic == BCMDB_MAGIC_ACTIVE_SET);
BUG_ON(!cb);
nf_new = bcmos_calloc(sizeof(bcmdb_notify));
if (!nf_new)
return BCM_ERR_NOMEM;
nf_new->cb = cb;
nf_new->cb_priv = cb_priv;
/* Add to set's notification list */
nf = sor->notify_list_head;
while(nf)
{
nf_prev = nf;
nf = nf->next;
}
if (nf_prev)
nf_prev->next = nf_new;
else
sor->notify_list_head = nf_new;
return 0;
}
/** Data base iterator
*
* \param[in] set data base set
* \param[in] prev last entry. BCMDB_KEY_ANY=start from the beginning
* \return data base entry key following prev or BCMDB_KEY_NO_MORE if end is reached.\n
* BCMDB_KEY_INVAL is reqturned if prev key is invalid
* \ingroup bcmdb
*/
bcmdb_key bcmdb_set_iterate(const bcmdb_set *set, bcmdb_key prev)
{
BUG_ON(!set);
BUG_ON(!set->magic == BCMDB_MAGIC_ACTIVE_SET);
return set->entry_get_next(set, prev);
}
/* Print database structure */
static void _bcmdb_print_structure(const bcmdb_set *set, int level)
{
int i;
if (!set)
return;
/* Indentation */
for(i=0; i<level; i++)
printf("\t");
if ((set->entry.flags & BCMDB_FLAG_SOS))
{
bcmdb_key key = bcmdb_set_iterate(set, BCMDB_KEY_ANY);
printf("%-16s SoS max_entries=%d entries=%d\n", set->name, set->max_entries, set->num_entries);
while(key >= 0)
{
_bcmdb_print_structure(bcmdb_set_handle(set, key), level+1);
key = bcmdb_set_iterate(set, key);
}
}
else
{
printf("%-16s SoR max_entries=%d entries=%d record_size=%d total_size=%d\n",
set->name, set->max_entries, set->num_entries, set->entry_size,
set->entry_size*set->max_entries);
}
}
/** Print database structure.
*
* \param[in] set root set
* \ingroup bcmdb
*/
void bcmdb_set_print_structure(const bcmdb_set *set)
{
_bcmdb_print_structure(set, 0);
}
/** Format record for printing.
*
* The function converts record data to human-readible format.
*
* \param[in] sor parent set of records
* \param[in] key record key
* \param[out] buffer output buffer
* \param[in] size buffer size
* \return
* >=0-amount of data placed in the buffer\n
* <0-error code
*/
int bcmdb_record_read_formatted(bcmdb_set *sor, bcmdb_key key, char *buffer, int size)
{
const void *data;
int len;
if (!buffer || !size)
return BCM_ERR_PARM;
if (!sor->format)
return BCM_ERR_NOT_SUPPORTED;
*buffer=0;
data = bcmdb_record_getraw_read(sor, key);
if (!data)
return 0;
len = sor->format(data, buffer, size);
bcmdb_record_unlock_read(sor, key);
return len;
}