+ struct ast_region *reg;
+ size_t total_length;
+ int idx;
+
+ total_length = 0;
+ for (idx = 0; idx < ARRAY_LEN(regions); ++idx) {
+ while ((reg = regions[idx])) {
+ regions[idx] = AST_LIST_NEXT(reg, node);
+ AST_LIST_NEXT(reg, node) = NULL;
+ AST_LIST_INSERT_HEAD(list, reg, node);
+ ++total_length;
+ }
+ }
+ return total_length;
+}
+
+/*!
+ * \internal
+ * \brief Put the regions list into the allocated regions hash table.
+ *
+ * \param list List to put into the allocated regions hash table.
+ *
+ * \note reglock must be locked before calling.
+ *
+ * \return Nothing
+ */
+static void mm_atexit_hash_restore(struct region_list *list)
+{
+ struct ast_region *reg;
+ int hash;
+
+ while ((reg = AST_LIST_REMOVE_HEAD(list, node))) {
+ hash = HASH(reg->data);
+ AST_LIST_NEXT(reg, node) = regions[hash];
+ regions[hash] = reg;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Sort regions comparision.
+ *
+ * \param left Region to compare.
+ * \param right Region to compare.
+ *
+ * \retval <0 if left < right
+ * \retval =0 if left == right
+ * \retval >0 if left > right
+ */
+static int mm_atexit_cmp(struct ast_region *left, struct ast_region *right)
+{
+ int cmp;
+ ptrdiff_t cmp_ptr;
+ ssize_t cmp_size;
+
+ /* Sort by filename. */
+ cmp = strcmp(left->file, right->file);
+ if (cmp) {
+ return cmp;
+ }
+
+ /* Sort by line number. */
+ cmp = left->lineno - right->lineno;
+ if (cmp) {
+ return cmp;
+ }
+
+ /* Sort by allocated size. */
+ cmp_size = left->len - right->len;
+ if (cmp_size) {
+ if (cmp_size < 0) {
+ return -1;
+ }
+ return 1;
+ }
+
+ /* Sort by allocated pointers just because. */
+ cmp_ptr = left->data - right->data;
+ if (cmp_ptr) {
+ if (cmp_ptr < 0) {
+ return -1;
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Merge the given sorted sublists into sorted order onto the end of the list.
+ *
+ * \param list Merge sublists onto this list.
+ * \param sub1 First sublist to merge.
+ * \param sub2 Second sublist to merge.
+ *
+ * \return Nothing
+ */
+static void mm_atexit_list_merge(struct region_list *list, struct region_list *sub1, struct region_list *sub2)
+{
+ struct ast_region *reg;
+
+ for (;;) {
+ if (AST_LIST_EMPTY(sub1)) {
+ /* The remaining sublist goes onto the list. */
+ AST_LIST_APPEND_LIST(list, sub2, node);
+ break;
+ }
+ if (AST_LIST_EMPTY(sub2)) {
+ /* The remaining sublist goes onto the list. */
+ AST_LIST_APPEND_LIST(list, sub1, node);
+ break;
+ }
+
+ if (mm_atexit_cmp(AST_LIST_FIRST(sub1), AST_LIST_FIRST(sub2)) <= 0) {
+ reg = AST_LIST_REMOVE_HEAD(sub1, node);
+ } else {
+ reg = AST_LIST_REMOVE_HEAD(sub2, node);
+ }
+ AST_LIST_INSERT_TAIL(list, reg, node);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Take sublists off of the given list.
+ *
+ * \param list Source list to remove sublists from the beginning of list.
+ * \param sub Array of sublists to fill. (Lists are empty on entry.)
+ * \param num_lists Number of lists to remove from the source list.
+ * \param size Size of the sublists to remove.
+ * \param remaining Remaining number of elements on the source list.
+ *
+ * \return Nothing
+ */
+static void mm_atexit_list_split(struct region_list *list, struct region_list sub[], size_t num_lists, size_t size, size_t *remaining)
+{
+ int idx;
+
+ for (idx = 0; idx < num_lists; ++idx) {
+ size_t count;
+
+ if (*remaining < size) {
+ /* The remaining source list goes onto the sublist. */
+ AST_LIST_APPEND_LIST(&sub[idx], list, node);
+ *remaining = 0;
+ break;
+ }
+
+ /* Take a sublist off the beginning of the source list. */
+ *remaining -= size;
+ for (count = size; count--;) {
+ struct ast_region *reg;
+
+ reg = AST_LIST_REMOVE_HEAD(list, node);
+ AST_LIST_INSERT_TAIL(&sub[idx], reg, node);
+ }
+ }
+}
+
+/*!
+ * \internal
+ * \brief Sort the regions list using mergesort.
+ *
+ * \param list Allocated regions list to sort.
+ * \param length Length of the list.
+ *
+ * \return Nothing
+ */
+static void mm_atexit_list_sort(struct region_list *list, size_t length)
+{
+ /*! Semi-sorted merged list. */
+ struct region_list merged = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
+ /*! Sublists to merge. (Can only merge two sublists at this time.) */
+ struct region_list sub[2] = {
+ AST_LIST_HEAD_NOLOCK_INIT_VALUE,
+ AST_LIST_HEAD_NOLOCK_INIT_VALUE
+ };
+ /*! Sublist size. */
+ size_t size = 1;
+ /*! Remaining elements in the list. */
+ size_t remaining;
+ /*! Number of sublist merge passes to process the list. */
+ int passes;
+
+ for (;;) {
+ remaining = length;
+
+ passes = 0;
+ while (!AST_LIST_EMPTY(list)) {
+ mm_atexit_list_split(list, sub, ARRAY_LEN(sub), size, &remaining);
+ mm_atexit_list_merge(&merged, &sub[0], &sub[1]);
+ ++passes;
+ }
+ AST_LIST_APPEND_LIST(list, &merged, node);
+ if (passes <= 1) {
+ /* The list is now sorted. */
+ break;
+ }
+
+ /* Double the sublist size to remove for next round. */
+ size <<= 1;
+ }
+}
+
+/*!
+ * \internal
+ * \brief List all regions currently allocated.
+ *
+ * \param alloced regions list.
+ *
+ * \return Nothing
+ */
+static void mm_atexit_regions_list(struct region_list *alloced)
+{
+ struct ast_region *reg;
+
+ AST_LIST_TRAVERSE(alloced, reg, node) {
+ astmm_log("%s %s() line %u: %u bytes%s at %p\n",
+ reg->file, reg->func, reg->lineno,
+ (unsigned int) reg->len, reg->cache ? " (cache)" : "", reg->data);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Summarize all regions currently allocated.
+ *
+ * \param alloced Sorted regions list.
+ *
+ * \return Nothing
+ */
+static void mm_atexit_regions_summary(struct region_list *alloced)
+{
+ struct ast_region *reg;
+ struct ast_region *next;
+ struct {
+ unsigned int count;
+ unsigned int len;
+ unsigned int cache_len;
+ } by_line, by_func, by_file, total;
+
+ by_line.count = 0;
+ by_line.len = 0;
+ by_line.cache_len = 0;
+
+ by_func.count = 0;
+ by_func.len = 0;
+ by_func.cache_len = 0;
+
+ by_file.count = 0;
+ by_file.len = 0;
+ by_file.cache_len = 0;
+
+ total.count = 0;
+ total.len = 0;
+ total.cache_len = 0;
+
+ AST_LIST_TRAVERSE(alloced, reg, node) {
+ next = AST_LIST_NEXT(reg, node);
+
+ ++by_line.count;
+ by_line.len += reg->len;
+ if (reg->cache) {
+ by_line.cache_len += reg->len;
+ }
+ if (next && !strcmp(reg->file, next->file) && reg->lineno == next->lineno) {
+ continue;
+ }
+ if (atexit_summary & SUMMARY_BY_LINE) {
+ if (by_line.cache_len) {
+ astmm_log("%10u bytes (%u in caches) in %u allocations. %s %s() line %u\n",
+ by_line.len, by_line.cache_len, by_line.count, reg->file, reg->func, reg->lineno);
+ } else {
+ astmm_log("%10u bytes in %5u allocations. %s %s() line %u\n",
+ by_line.len, by_line.count, reg->file, reg->func, reg->lineno);
+ }
+ }
+
+ by_func.count += by_line.count;
+ by_func.len += by_line.len;
+ by_func.cache_len += by_line.cache_len;
+ by_line.count = 0;
+ by_line.len = 0;
+ by_line.cache_len = 0;
+ if (next && !strcmp(reg->file, next->file) && !strcmp(reg->func, next->func)) {
+ continue;
+ }
+ if (atexit_summary & SUMMARY_BY_FUNC) {
+ if (by_func.cache_len) {
+ astmm_log("%10u bytes (%u in caches) in %u allocations. %s %s()\n",
+ by_func.len, by_func.cache_len, by_func.count, reg->file, reg->func);
+ } else {
+ astmm_log("%10u bytes in %5u allocations. %s %s()\n",
+ by_func.len, by_func.count, reg->file, reg->func);
+ }
+ }