summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/asterisk/stringfields.h89
-rw-r--r--main/utils.c153
2 files changed, 166 insertions, 76 deletions
diff --git a/include/asterisk/stringfields.h b/include/asterisk/stringfields.h
index 9c48bfda2..2b9b7b230 100644
--- a/include/asterisk/stringfields.h
+++ b/include/asterisk/stringfields.h
@@ -57,25 +57,23 @@
Fields will default to pointing to an empty string, and will revert to
that when ast_string_field_set() is called with a NULL argument.
- A string field will \b never contain NULL (this feature is not used
- in this code, but comes from external requirements).
+ A string field will \b never contain NULL.
ast_string_field_init(x, 0) will reset fields to the
initial value while keeping the pool allocated.
Reading the fields is much like using 'const char * const' fields in the
- structure: you cannot write to the field or to the memory it points to
- (XXX perhaps the latter is too much of a restriction since values
- are not shared).
+ structure: you cannot write to the field or to the memory it points to.
Writing to the fields must be done using the wrapper macros listed below;
and assignments are always by value (i.e. strings are copied):
* ast_string_field_set() stores a simple value;
- * ast_string_field_build() builds the string using a printf-style;
+ * ast_string_field_build() builds the string using a printf-style format;
* ast_string_field_build_va() is the varargs version of the above (for
- portability reasons it uses two vararg);
+ portability reasons it uses two vararg arguments);
* variants of these function allow passing a pointer to the field
as an argument.
+
\code
ast_string_field_set(x, foo, "infinite loop");
ast_string_field_set(x, foo, NULL); // set to an empty string
@@ -110,6 +108,9 @@
Don't declare instances of this type directly; use the AST_STRING_FIELD()
macro instead.
+
+ In addition to the string itself, the amount of space allocated for the
+ field is stored in the two bytes immediately preceding it.
*/
typedef const char * ast_string_field;
@@ -117,7 +118,7 @@ typedef const char * ast_string_field;
\internal
\brief A constant empty string used for fields that have no other value
*/
-extern const char __ast_string_field_empty[];
+extern const char *__ast_string_field_empty;
/*!
\internal
@@ -125,18 +126,17 @@ extern const char __ast_string_field_empty[];
*/
struct ast_string_field_pool {
struct ast_string_field_pool *prev; /*!< pointer to the previous pool, if any */
+ size_t size; /*!< the total size of the pool */
+ size_t used; /*!< the space used in the pool */
+ size_t active; /*!< the amount of space actively in use by fields */
char base[0]; /*!< storage space for the fields */
};
/*!
\internal
\brief Structure used to manage the storage for a set of string fields.
- Because of the way pools are managed, we can only allocate from the topmost
- pool, so the numbers here reflect just that.
*/
struct ast_string_field_mgr {
- size_t size; /*!< the total size of the current pool */
- size_t used; /*!< the space used in the current pool */
ast_string_field last_alloc; /*!< the last field allocated */
};
@@ -154,7 +154,8 @@ struct ast_string_field_mgr {
the pool has enough space available. If so, the additional space will be
allocated to this field and the field's address will not be changed.
*/
-int __ast_string_field_ptr_grow(struct ast_string_field_mgr *mgr, size_t needed,
+int __ast_string_field_ptr_grow(struct ast_string_field_mgr *mgr,
+ struct ast_string_field_pool **pool_head, size_t needed,
const ast_string_field *ptr);
/*!
@@ -176,7 +177,7 @@ ast_string_field __ast_string_field_alloc_space(struct ast_string_field_mgr *mgr
\internal
\brief Set a field to a complex (built) value
\param mgr Pointer to the pool manager structure
- \param fields Pointer to the first entry of the field array
+ \param pool_head Pointer to the current pool
\param ptr Pointer to a field within the structure
\param format printf-style format string
\return nothing
@@ -189,7 +190,7 @@ void __ast_string_field_ptr_build(struct ast_string_field_mgr *mgr,
\internal
\brief Set a field to a complex (built) value
\param mgr Pointer to the pool manager structure
- \param fields Pointer to the first entry of the field array
+ \param pool_head Pointer to the current pool
\param ptr Pointer to a field within the structure
\param format printf-style format string
\param args va_list of the args for the format_string
@@ -242,29 +243,55 @@ void __ast_string_field_ptr_build_va(struct ast_string_field_mgr *mgr,
/*! \internal \brief internal version of ast_string_field_init */
int __ast_string_field_init(struct ast_string_field_mgr *mgr,
- struct ast_string_field_pool **pool_head, int needed);
+ struct ast_string_field_pool **pool_head, int needed);
+
+/*!
+ \internal
+ \brief Release a field's allocation from a pool
+ \param pool_head Pointer to the current pool
+ \param ptr Field to be released
+ \return nothing
+
+ This function will search the pool list to find the pool that contains
+ the allocation for the specified field, then remove the field's allocation
+ from that pool's 'active' count. If the pool's active count reaches zero,
+ and it is not the current pool, then it will be freed.
+ */
+void __ast_string_field_release_active(struct ast_string_field_pool *pool_head,
+ const ast_string_field ptr);
+
+/* the type of storage used to track how many bytes were allocated for a field */
+
+typedef uint16_t ast_string_field_allocation;
+
+/*!
+ \brief Macro to provide access to the allocation field that lives immediately in front of a string field
+ \param x Pointer to the string field
+*/
+#define AST_STRING_FIELD_ALLOCATION(x) *((ast_string_field_allocation *) (x - sizeof(ast_string_field_allocation)))
/*!
\brief Set a field to a simple string value
\param x Pointer to a structure containing fields
\param ptr Pointer to a field within the structure
- \param data String value to be copied into the field
+ \param data String value to be copied into the field
\return nothing
*/
-#define ast_string_field_ptr_set(x, ptr, data) do { \
- const char *__d__ = (data); \
- size_t __dlen__ = (__d__) ? strlen(__d__) + 1 : 1; \
- const char **__p__ = (const char **) (ptr); \
- char *__q__; \
- if (__dlen__ == 1) \
- *__p__ = __ast_string_field_empty; \
- else if (!__ast_string_field_ptr_grow(&(x)->__field_mgr, __dlen__, ptr)) { \
- __q__ = (char *) *__p__; \
- memcpy(__q__, __d__, __dlen__); \
- } else if ((*__p__ = __ast_string_field_alloc_space(&(x)->__field_mgr, &(x)->__field_mgr_pool, __dlen__))) { \
- __q__ = (char *) *__p__; \
- memcpy(__q__, __d__, __dlen__); \
- } \
+#define ast_string_field_ptr_set(x, ptr, data) do { \
+ const char *__d__ = (data); \
+ size_t __dlen__ = (__d__) ? strlen(__d__) + 1 : 1; \
+ ast_string_field *__p__ = (ast_string_field *) (ptr); \
+ if (__dlen__ == 1) { \
+ __ast_string_field_release_active((x)->__field_mgr_pool, *__p__); \
+ *__p__ = __ast_string_field_empty; \
+ } else if ((__dlen__ <= AST_STRING_FIELD_ALLOCATION(*__p__)) || \
+ (!__ast_string_field_ptr_grow(&(x)->__field_mgr, &(x)->__field_mgr_pool, __dlen__, __p__)) || \
+ (*__p__ = __ast_string_field_alloc_space(&(x)->__field_mgr, &(x)->__field_mgr_pool, __dlen__))) { \
+ if (*__p__ != (*ptr)) { \
+ __ast_string_field_release_active((x)->__field_mgr_pool, (*ptr)); \
+ } \
+ memcpy((void *) *__p__, __d__, __dlen__); \
+ } \
} while (0)
/*!
diff --git a/main/utils.c b/main/utils.c
index 0e05c4d3c..60da56250 100644
--- a/main/utils.c
+++ b/main/utils.c
@@ -1469,7 +1469,32 @@ void ast_join(char *s, size_t len, char * const w[])
* stringfields support routines.
*/
-const char __ast_string_field_empty[] = ""; /*!< the empty string */
+/* this is a little complex... string fields are stored with their
+ allocated size in the bytes preceding the string; even the
+ constant 'empty' string has to be this way, so the code that
+ checks to see if there is enough room for a new string doesn't
+ have to have any special case checks
+*/
+
+static const struct {
+ ast_string_field_allocation allocation;
+ char string[1];
+} __ast_string_field_empty_buffer;
+
+ast_string_field __ast_string_field_empty = __ast_string_field_empty_buffer.string;
+
+#define ALLOCATOR_OVERHEAD 48
+
+static size_t optimal_alloc_size(size_t size)
+{
+ unsigned int count;
+
+ size += ALLOCATOR_OVERHEAD;
+
+ for (count = 1; size; size >>= 1, count++);
+
+ return (1 << count) - ALLOCATOR_OVERHEAD;
+}
/*! \brief add a new block to the pool.
* We can only allocate from the topmost pool, so the
@@ -1480,14 +1505,15 @@ static int add_string_pool(struct ast_string_field_mgr *mgr,
size_t size)
{
struct ast_string_field_pool *pool;
+ size_t alloc_size = optimal_alloc_size(sizeof(*pool) + size);
- if (!(pool = ast_calloc(1, sizeof(*pool) + size)))
+ if (!(pool = ast_calloc(1, alloc_size))) {
return -1;
-
+ }
+
pool->prev = *pool_head;
+ pool->size = alloc_size - sizeof(*pool);
*pool_head = pool;
- mgr->size = size;
- mgr->used = 0;
mgr->last_alloc = NULL;
return 0;
@@ -1511,13 +1537,16 @@ int __ast_string_field_init(struct ast_string_field_mgr *mgr,
struct ast_string_field_pool *cur = *pool_head;
/* clear fields - this is always necessary */
- while ((struct ast_string_field_mgr *) p != mgr)
+ while ((struct ast_string_field_mgr *) p != mgr) {
*p++ = __ast_string_field_empty;
+ }
+
mgr->last_alloc = NULL;
if (size > 0) { /* allocate the initial pool */
*pool_head = NULL;
return add_string_pool(mgr, pool_head, size);
}
+
if (size < 0) { /* reset all pools */
*pool_head = NULL;
} else { /* preserve the first pool */
@@ -1527,7 +1556,7 @@ int __ast_string_field_init(struct ast_string_field_mgr *mgr,
}
cur = cur->prev;
(*pool_head)->prev = NULL;
- mgr->used = 0;
+ (*pool_head)->used = (*pool_head)->active = 0;
}
while (cur) {
@@ -1544,33 +1573,36 @@ ast_string_field __ast_string_field_alloc_space(struct ast_string_field_mgr *mgr
struct ast_string_field_pool **pool_head, size_t needed)
{
char *result = NULL;
- size_t space = mgr->size - mgr->used;
+ size_t space = (*pool_head)->size - (*pool_head)->used;
+ size_t to_alloc = needed + sizeof(ast_string_field_allocation);
- if (__builtin_expect(needed > space, 0)) {
- size_t new_size = mgr->size * 2;
+ if (__builtin_expect(to_alloc > space, 0)) {
+ size_t new_size = (*pool_head)->size;
- while (new_size < needed)
+ while (new_size < to_alloc) {
new_size *= 2;
+ }
if (add_string_pool(mgr, pool_head, new_size))
return NULL;
}
- result = (*pool_head)->base + mgr->used;
- mgr->used += needed;
+ result = (*pool_head)->base + (*pool_head)->used;
+ (*pool_head)->used += to_alloc;
+ (*pool_head)->active += needed;
+ result += sizeof(ast_string_field_allocation);
+ AST_STRING_FIELD_ALLOCATION(result) = needed;
mgr->last_alloc = result;
+
return result;
}
-int __ast_string_field_ptr_grow(struct ast_string_field_mgr *mgr, size_t needed,
+int __ast_string_field_ptr_grow(struct ast_string_field_mgr *mgr,
+ struct ast_string_field_pool **pool_head, size_t needed,
const ast_string_field *ptr)
{
- int grow = needed - (strlen(*ptr) + 1);
- size_t space = mgr->size - mgr->used;
-
- if (grow <= 0) {
- return 0;
- }
+ ssize_t grow = needed - AST_STRING_FIELD_ALLOCATION(*ptr);
+ size_t space = (*pool_head)->size - (*pool_head)->used;
if (*ptr != mgr->last_alloc) {
return 1;
@@ -1580,30 +1612,57 @@ int __ast_string_field_ptr_grow(struct ast_string_field_mgr *mgr, size_t needed,
return 1;
}
- mgr->used += grow;
+ (*pool_head)->used += grow;
+ (*pool_head)->active += grow;
+ AST_STRING_FIELD_ALLOCATION(*ptr) += grow;
return 0;
}
+void __ast_string_field_release_active(struct ast_string_field_pool *pool_head,
+ const ast_string_field ptr)
+{
+ struct ast_string_field_pool *pool, *prev;
+
+ if (ptr == __ast_string_field_empty) {
+ return;
+ }
+
+ for (pool = pool_head, prev = NULL; pool; prev = pool, pool = pool->prev) {
+ if ((ptr >= pool->base) && (ptr <= (pool->base + pool->size))) {
+ pool->active -= AST_STRING_FIELD_ALLOCATION(ptr);
+ if ((pool->active == 0) && prev) {
+ prev->prev = pool->prev;
+ ast_free(pool);
+ }
+ break;
+ }
+ }
+}
+
void __ast_string_field_ptr_build_va(struct ast_string_field_mgr *mgr,
struct ast_string_field_pool **pool_head,
ast_string_field *ptr, const char *format, va_list ap1, va_list ap2)
{
size_t needed;
size_t available;
- size_t space = mgr->size - mgr->used;
+ size_t space = (*pool_head)->size - (*pool_head)->used;
+ ssize_t grow;
char *target;
/* if the field already has space allocated, try to reuse it;
- otherwise, use the empty space at the end of the current
+ otherwise, try to use the empty space at the end of the current
pool
*/
- if ((*ptr)[0] != '\0') {
+ if (*ptr != __ast_string_field_empty) {
target = (char *) *ptr;
- available = strlen(target) + 1;
+ available = AST_STRING_FIELD_ALLOCATION(*ptr);
+ if (*ptr == mgr->last_alloc) {
+ available += space;
+ }
} else {
- target = (*pool_head)->base + mgr->used;
- available = space;
+ target = (*pool_head)->base + (*pool_head)->used + sizeof(ast_string_field_allocation);
+ available = space - sizeof(ast_string_field_allocation);
}
needed = vsnprintf(target, available, format, ap1) + 1;
@@ -1611,28 +1670,32 @@ void __ast_string_field_ptr_build_va(struct ast_string_field_mgr *mgr,
va_end(ap1);
if (needed > available) {
- /* if the space needed can be satisfied by using the current
- pool (which could only occur if we tried to use the field's
- allocated space and failed), then use that space; otherwise
- allocate a new pool
+ /* the allocation could not be satisfied using the field's current allocation
+ (if it has one), or the space available in the pool (if it does not). allocate
+ space for it, adding a new string pool if necessary.
*/
- if (needed > space) {
- size_t new_size = mgr->size * 2;
-
- while (new_size < needed)
- new_size *= 2;
-
- if (add_string_pool(mgr, pool_head, new_size))
- return;
+ if (!(target = (char *) __ast_string_field_alloc_space(mgr, pool_head, needed))) {
+ return;
}
-
- target = (*pool_head)->base + mgr->used;
vsprintf(target, format, ap2);
- }
-
- if (*ptr != target) {
+ __ast_string_field_release_active(*pool_head, *ptr);
+ *ptr = target;
+ } else if (*ptr != target) {
+ /* the allocation was satisfied using available space in the pool, but not
+ using the space already allocated to the field
+ */
+ __ast_string_field_release_active(*pool_head, *ptr);
mgr->last_alloc = *ptr = target;
- mgr->used += needed;
+ AST_STRING_FIELD_ALLOCATION(target) = needed;
+ (*pool_head)->used += needed + sizeof(ast_string_field_allocation);
+ (*pool_head)->active += needed;
+ } else if ((grow = (needed - AST_STRING_FIELD_ALLOCATION(*ptr))) > 0) {
+ /* the allocation was satisfied by using available space in the pool *and*
+ the field was the last allocated field from the pool, so it grew
+ */
+ (*pool_head)->used += grow;
+ (*pool_head)->active += grow;
+ AST_STRING_FIELD_ALLOCATION(*ptr) += grow;
}
}