summaryrefslogtreecommitdiffstats
path: root/src/lib/data-stack.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib/data-stack.c777
1 files changed, 777 insertions, 0 deletions
diff --git a/src/lib/data-stack.c b/src/lib/data-stack.c
new file mode 100644
index 0000000..acbedc9
--- /dev/null
+++ b/src/lib/data-stack.c
@@ -0,0 +1,777 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+/* @UNSAFE: whole file */
+
+#include "lib.h"
+#include "backtrace-string.h"
+#include "str.h"
+#include "data-stack.h"
+
+
+/* Initial stack size - this should be kept in a size that doesn't exceed
+ in a normal use to avoid extra malloc()ing. */
+#ifdef DEBUG
+# define INITIAL_STACK_SIZE (1024*10)
+#else
+# define INITIAL_STACK_SIZE (1024*32)
+#endif
+
+#ifdef DEBUG
+# define CLEAR_CHR 0xD5 /* D5 is mnemonic for "Data 5tack" */
+# define SENTRY_COUNT (4*8)
+# define BLOCK_CANARY ((void *)0xBADBADD5BADBADD5) /* contains 'D5' */
+# define ALLOC_SIZE(size) (MEM_ALIGN(sizeof(size_t)) + MEM_ALIGN(size + SENTRY_COUNT))
+#else
+# define CLEAR_CHR 0
+# define BLOCK_CANARY NULL
+# define block_canary_check(block) do { ; } while(0)
+# define ALLOC_SIZE(size) MEM_ALIGN(size)
+#endif
+
+struct stack_block {
+ struct stack_block *prev, *next;
+
+ size_t size, left;
+#ifdef DEBUG
+ /* The lowest value that "left" has been in this block since it was
+ last popped. This is used to keep track which parts of the block
+ needs to be cleared if DEBUG is used. */
+ size_t left_lowwater;
+#endif
+ /* NULL or a poison value, just in case something accesses
+ the memory in front of an allocated area */
+ void *canary;
+ unsigned char data[FLEXIBLE_ARRAY_MEMBER];
+};
+
+#define SIZEOF_MEMBLOCK MEM_ALIGN(sizeof(struct stack_block))
+
+#define STACK_BLOCK_DATA(block) \
+ (block->data + (SIZEOF_MEMBLOCK - sizeof(struct stack_block)))
+
+struct stack_frame {
+ struct stack_frame *prev;
+
+ struct stack_block *block;
+ /* Each frame initializes this to current_block->left, i.e. how much
+ free space is left in the block. So the frame's start position in
+ the block is (block.size - block_space_left) */
+ size_t block_space_left;
+ size_t last_alloc_size;
+ const char *marker;
+#ifdef DEBUG
+ /* Fairly arbitrary profiling data */
+ unsigned long long alloc_bytes;
+ unsigned int alloc_count;
+#endif
+};
+
+#ifdef STATIC_CHECKER
+struct data_stack_frame {
+ unsigned int id;
+};
+#endif
+
+unsigned int data_stack_frame_id = 0;
+
+static bool data_stack_initialized = FALSE;
+static data_stack_frame_t root_frame_id;
+
+static struct stack_frame *current_frame;
+
+/* The latest block currently used for allocation. current_block->next is
+ always NULL. */
+static struct stack_block *current_block;
+/* The largest block that data stack has allocated so far, which was already
+ freed. This can prevent rapid malloc()+free()ing when data stack is grown
+ and shrunk constantly. */
+static struct stack_block *unused_block = NULL;
+
+static struct event *event_datastack = NULL;
+static bool event_datastack_deinitialized = FALSE;
+
+static struct stack_block *last_buffer_block;
+static size_t last_buffer_size;
+static bool outofmem = FALSE;
+
+static union {
+ struct stack_block block;
+ unsigned char data[512];
+} outofmem_area;
+
+static struct stack_block *mem_block_alloc(size_t min_size);
+
+static inline
+unsigned char *data_stack_after_last_alloc(struct stack_block *block)
+{
+ return STACK_BLOCK_DATA(block) + (block->size - block->left);
+}
+
+static void data_stack_last_buffer_reset(bool preserve_data ATTR_UNUSED)
+{
+ if (last_buffer_block != NULL) {
+#ifdef DEBUG
+ unsigned char *last_alloc_end, *p, *pend;
+
+ /* We assume that this function gets called before
+ current_block changes. */
+ i_assert(last_buffer_block == current_block);
+
+ last_alloc_end = data_stack_after_last_alloc(current_block);
+ p = last_alloc_end + MEM_ALIGN(sizeof(size_t)) + last_buffer_size;
+ pend = last_alloc_end + ALLOC_SIZE(last_buffer_size);
+#endif
+ /* reset t_buffer_get() mark - not really needed but makes it
+ easier to notice if t_malloc()/t_push()/t_pop() is called
+ between t_buffer_get() and t_buffer_alloc().
+ do this before we get to i_panic() to avoid recursive
+ panics. */
+ last_buffer_block = NULL;
+
+#ifdef DEBUG
+ /* NOTE: If the below panic triggers, it may also be due to an
+ internal bug in data-stack (since this is rather complex). While
+ debugging whether that is the case, it's a good idea to change the
+ i_panic() to abort(). Otherwise the i_panic() changes the
+ data-stack's internal state and complicates debugging. */
+ while (p < pend)
+ if (*p++ != CLEAR_CHR)
+ i_panic("t_buffer_get(): buffer overflow");
+
+ if (!preserve_data) {
+ p = last_alloc_end;
+ memset(p, CLEAR_CHR, SENTRY_COUNT);
+ }
+#endif
+ }
+}
+
+data_stack_frame_t t_push(const char *marker)
+{
+ struct stack_frame *frame;
+
+ i_assert(marker != NULL);
+
+ if (unlikely(!data_stack_initialized)) {
+ /* kludgy, but allow this before initialization */
+ data_stack_init();
+ return t_push(marker);
+ }
+
+ /* allocate new block */
+ frame = t_buffer_get(sizeof(*frame));
+ frame->prev = current_frame;
+ current_frame = frame;
+
+ /* mark our current position */
+ current_frame->block = current_block;
+ current_frame->block_space_left = current_block->left;
+ current_frame->last_alloc_size = 0;
+ current_frame->marker = marker;
+#ifdef DEBUG
+ current_frame->alloc_bytes = 0;
+ current_frame->alloc_count = 0;
+#endif
+
+ t_buffer_alloc(sizeof(*frame));
+
+#ifndef STATIC_CHECKER
+ return data_stack_frame_id++;
+#else
+ struct data_stack_frame *ds_frame = i_new(struct data_stack_frame, 1);
+ ds_frame->id = data_stack_frame_id++;
+ return ds_frame;
+#endif
+}
+
+data_stack_frame_t t_push_named(const char *format, ...)
+{
+ data_stack_frame_t ret = t_push(format);
+#ifdef DEBUG
+ va_list args;
+ va_start(args, format);
+ current_frame->marker = p_strdup_vprintf(unsafe_data_stack_pool, format, args);
+ va_end(args);
+#else
+ (void)format; /* unused in non-DEBUG builds */
+#endif
+
+ return ret;
+}
+
+#ifdef DEBUG
+static void block_canary_check(struct stack_block *block)
+{
+ if (block->canary != BLOCK_CANARY) {
+ /* Make sure i_panic() won't try to allocate from the
+ same block by falling back onto our emergency block. */
+ current_block = &outofmem_area.block;
+ i_panic("Corrupted data stack canary");
+ }
+}
+#endif
+
+static void free_blocks(struct stack_block *block)
+{
+ struct stack_block *next;
+
+ /* free all the blocks, except if any of them is bigger than
+ unused_block, replace it */
+ while (block != NULL) {
+ block_canary_check(block);
+ next = block->next;
+
+#ifdef DEBUG
+ memset(STACK_BLOCK_DATA(block), CLEAR_CHR, block->size);
+#endif
+
+ if (block == &outofmem_area.block)
+ ;
+ else if (unused_block == NULL ||
+ block->size > unused_block->size) {
+ free(unused_block);
+ unused_block = block;
+ } else {
+ free(block);
+ }
+
+ block = next;
+ }
+}
+
+#ifdef DEBUG
+static void t_pop_verify(void)
+{
+ struct stack_block *block;
+ unsigned char *p;
+ size_t pos, max_pos, used_size;
+
+ block = current_frame->block;
+ pos = block->size - current_frame->block_space_left;
+ while (block != NULL) {
+ block_canary_check(block);
+ used_size = block->size - block->left;
+ p = STACK_BLOCK_DATA(block);
+ while (pos < used_size) {
+ size_t requested_size = *(size_t *)(p + pos);
+ if (used_size - pos < requested_size)
+ i_panic("data stack[%s]: saved alloc size broken",
+ current_frame->marker);
+ max_pos = pos + ALLOC_SIZE(requested_size);
+ pos += MEM_ALIGN(sizeof(size_t)) + requested_size;
+
+ for (; pos < max_pos; pos++) {
+ if (p[pos] != CLEAR_CHR)
+ i_panic("data stack[%s]: buffer overflow",
+ current_frame->marker);
+ }
+ }
+
+ /* if we had used t_buffer_get(), the rest of the buffer
+ may not contain CLEAR_CHRs. but we've already checked all
+ the allocations, so there's no need to check them anyway. */
+ block = block->next;
+ pos = 0;
+ }
+}
+#endif
+
+void t_pop_last_unsafe(void)
+{
+ size_t block_space_left;
+
+ if (unlikely(current_frame == NULL))
+ i_panic("t_pop() called with empty stack");
+
+ data_stack_last_buffer_reset(FALSE);
+#ifdef DEBUG
+ t_pop_verify();
+#endif
+
+ /* Usually the block doesn't change. If it doesn't, the next pointer
+ must also be NULL. */
+ if (current_block != current_frame->block) {
+ current_block = current_frame->block;
+ if (current_block->next != NULL) {
+ /* free unused blocks */
+ free_blocks(current_block->next);
+ current_block->next = NULL;
+ }
+ }
+ block_canary_check(current_block);
+
+ /* current_frame points inside the stack frame that will be freed.
+ make sure it's not accessed after it's already freed/cleaned. */
+ block_space_left = current_frame->block_space_left;
+ current_frame = current_frame->prev;
+
+#ifdef DEBUG
+ size_t start_pos, end_pos;
+
+ start_pos = current_block->size - block_space_left;
+ end_pos = current_block->size - current_block->left_lowwater;
+ i_assert(end_pos >= start_pos);
+ memset(STACK_BLOCK_DATA(current_block) + start_pos, CLEAR_CHR,
+ end_pos - start_pos);
+ current_block->left_lowwater = block_space_left;
+#endif
+
+ current_block->left = block_space_left;
+
+ data_stack_frame_id--;
+}
+
+bool t_pop(data_stack_frame_t *id)
+{
+ t_pop_last_unsafe();
+#ifndef STATIC_CHECKER
+ if (unlikely(data_stack_frame_id != *id))
+ return FALSE;
+ *id = 0;
+#else
+ unsigned int frame_id = (*id)->id;
+ i_free_and_null(*id);
+
+ if (unlikely(data_stack_frame_id != frame_id))
+ return FALSE;
+#endif
+ return TRUE;
+}
+
+bool t_pop_pass_str(data_stack_frame_t *id, const char **str)
+{
+ if (str == NULL || !data_stack_frame_contains(id, *str))
+ return t_pop(id);
+
+ /* FIXME: The string could be memmove()d to the beginning of the
+ data stack frame and the previous frame's size extended past it.
+ This would avoid the malloc. It's a bit complicated though. */
+ char *tmp_str = i_strdup(*str);
+ bool ret = t_pop(id);
+ *str = t_strdup(tmp_str);
+ i_free(tmp_str);
+ return ret;
+}
+
+static void mem_block_reset(struct stack_block *block)
+{
+ block->prev = NULL;
+ block->next = NULL;
+ block->left = block->size;
+#ifdef DEBUG
+ block->left_lowwater = block->size;
+#endif
+}
+
+static struct stack_block *mem_block_alloc(size_t min_size)
+{
+ struct stack_block *block;
+ size_t prev_size, alloc_size;
+
+ prev_size = current_block == NULL ? 0 : current_block->size;
+ /* Use INITIAL_STACK_SIZE without growing it to nearest power. */
+ alloc_size = prev_size == 0 ? min_size :
+ nearest_power(MALLOC_ADD(prev_size, min_size));
+
+ /* nearest_power() returns 2^n values, so alloc_size can't be
+ anywhere close to SIZE_MAX */
+ block = malloc(SIZEOF_MEMBLOCK + alloc_size);
+ if (unlikely(block == NULL)) {
+ if (outofmem) {
+ if (min_size > outofmem_area.block.left)
+ abort();
+ return &outofmem_area.block;
+ }
+ outofmem = TRUE;
+ i_panic("data stack: Out of memory when allocating %zu bytes",
+ alloc_size + SIZEOF_MEMBLOCK);
+ }
+ block->size = alloc_size;
+ block->canary = BLOCK_CANARY;
+ mem_block_reset(block);
+#ifdef DEBUG
+ memset(STACK_BLOCK_DATA(block), CLEAR_CHR, alloc_size);
+#endif
+ return block;
+}
+
+static void data_stack_send_grow_event(size_t last_alloc_size)
+{
+ if (event_datastack_deinitialized) {
+ /* already in the deinitialization code -
+ don't send more events */
+ return;
+ }
+ if (event_datastack == NULL)
+ event_datastack = event_create(NULL);
+ event_set_name(event_datastack, "data_stack_grow");
+ event_add_int(event_datastack, "alloc_size", data_stack_get_alloc_size());
+ event_add_int(event_datastack, "used_size", data_stack_get_used_size());
+ event_add_int(event_datastack, "last_alloc_size", last_alloc_size);
+ event_add_int(event_datastack, "last_block_size", current_block->size);
+#ifdef DEBUG
+ event_add_int(event_datastack, "frame_alloc_bytes",
+ current_frame->alloc_bytes);
+ event_add_int(event_datastack, "frame_alloc_count",
+ current_frame->alloc_count);
+#endif
+ event_add_str(event_datastack, "frame_marker", current_frame->marker);
+
+ /* It's possible that the data stack gets grown and shrunk rapidly.
+ Try to avoid doing expensive work if the event isn't even used for
+ anything. Note that at this point all the event fields must be
+ set already that might potentially be used by the filters. */
+ if (!event_want_debug(event_datastack))
+ return;
+
+ /* Getting backtrace is potentially inefficient, so do it after
+ checking if the event is wanted. Note that this prevents using the
+ backtrace field in event field comparisons. */
+ const char *backtrace;
+ if (backtrace_get(&backtrace) == 0)
+ event_add_str(event_datastack, "backtrace", backtrace);
+
+ string_t *str = t_str_new(128);
+ str_printfa(str, "total_used=%zu, total_alloc=%zu, last_alloc_size=%zu",
+ data_stack_get_used_size(),
+ data_stack_get_alloc_size(),
+ last_alloc_size);
+#ifdef DEBUG
+ str_printfa(str, ", frame_bytes=%llu, frame_alloc_count=%u",
+ current_frame->alloc_bytes, current_frame->alloc_count);
+#endif
+ e_debug(event_datastack, "Growing data stack by %zu for '%s' (%s)",
+ current_block->size, current_frame->marker,
+ str_c(str));
+}
+
+static void *t_malloc_real(size_t size, bool permanent)
+{
+ void *ret;
+ size_t alloc_size;
+ bool warn = FALSE;
+#ifdef DEBUG
+ int old_errno = errno;
+#endif
+
+ if (unlikely(size == 0 || size > SSIZE_T_MAX))
+ i_panic("Trying to allocate %zu bytes", size);
+
+ if (unlikely(!data_stack_initialized)) {
+ /* kludgy, but allow this before initialization */
+ data_stack_init();
+ }
+ block_canary_check(current_block);
+
+ /* allocate only aligned amount of memory so alignment comes
+ always properly */
+ alloc_size = ALLOC_SIZE(size);
+#ifdef DEBUG
+ if(permanent) {
+ current_frame->alloc_bytes += alloc_size;
+ current_frame->alloc_count++;
+ }
+#endif
+ data_stack_last_buffer_reset(TRUE);
+
+ if (permanent) {
+ /* used for t_try_realloc() */
+ current_frame->last_alloc_size = alloc_size;
+ }
+
+ if (current_block->left < alloc_size) {
+ struct stack_block *block;
+
+ /* current block is full, see if we can use the unused_block */
+ if (unused_block != NULL && unused_block->size >= alloc_size) {
+ block = unused_block;
+ unused_block = NULL;
+ mem_block_reset(block);
+ } else {
+ /* current block is full, allocate a new one */
+ block = mem_block_alloc(alloc_size);
+ warn = TRUE;
+ }
+
+ /* The newly allocated block will replace the current_block,
+ i.e. current_block always points to the last element in
+ the linked list. */
+ block->prev = current_block;
+ current_block->next = block;
+ current_block = block;
+ }
+
+ /* enough space in current block, use it */
+ ret = data_stack_after_last_alloc(current_block);
+
+#ifdef DEBUG
+ if (current_block->left - alloc_size < current_block->left_lowwater)
+ current_block->left_lowwater = current_block->left - alloc_size;
+#endif
+ if (permanent)
+ current_block->left -= alloc_size;
+
+ if (warn) T_BEGIN {
+ /* sending event can cause errno changes. */
+#ifdef DEBUG
+ i_assert(errno == old_errno);
+#else
+ int old_errno = errno;
+#endif
+ /* warn after allocation, so if e_debug() wants to
+ allocate more memory we don't go to infinite loop */
+ data_stack_send_grow_event(alloc_size);
+ /* reset errno back to what it was */
+ errno = old_errno;
+ } T_END;
+#ifdef DEBUG
+ memcpy(ret, &size, sizeof(size));
+ ret = PTR_OFFSET(ret, MEM_ALIGN(sizeof(size)));
+ /* make sure the sentry contains CLEAR_CHRs. it might not if we
+ had used t_buffer_get(). */
+ memset(PTR_OFFSET(ret, size), CLEAR_CHR,
+ MEM_ALIGN(size + SENTRY_COUNT) - size);
+
+ /* we rely on errno not changing. it shouldn't. */
+ i_assert(errno == old_errno);
+#endif
+ return ret;
+}
+
+void *t_malloc_no0(size_t size)
+{
+ return t_malloc_real(size, TRUE);
+}
+
+void *t_malloc0(size_t size)
+{
+ void *mem;
+
+ mem = t_malloc_real(size, TRUE);
+ memset(mem, 0, size);
+ return mem;
+}
+
+bool ATTR_NO_SANITIZE_INTEGER
+t_try_realloc(void *mem, size_t size)
+{
+ size_t debug_adjust = 0, last_alloc_size;
+ unsigned char *after_last_alloc;
+
+ if (unlikely(size == 0 || size > SSIZE_T_MAX))
+ i_panic("Trying to allocate %zu bytes", size);
+ block_canary_check(current_block);
+ data_stack_last_buffer_reset(TRUE);
+
+ last_alloc_size = current_frame->last_alloc_size;
+
+ /* see if we're trying to grow the memory we allocated last */
+ after_last_alloc = data_stack_after_last_alloc(current_block);
+#ifdef DEBUG
+ debug_adjust = MEM_ALIGN(sizeof(size_t));
+#endif
+ if (after_last_alloc - last_alloc_size + debug_adjust == mem) {
+ /* yeah, see if we have space to grow */
+ size_t new_alloc_size, alloc_growth;
+
+ new_alloc_size = ALLOC_SIZE(size);
+ alloc_growth = (new_alloc_size - last_alloc_size);
+#ifdef DEBUG
+ size_t old_raw_size; /* sorry, non-C99 users - add braces if you need them */
+ old_raw_size = *(size_t *)PTR_OFFSET(mem, -(ptrdiff_t)MEM_ALIGN(sizeof(size_t)));
+ i_assert(ALLOC_SIZE(old_raw_size) == last_alloc_size);
+ /* Only check one byte for over-run, that catches most
+ offenders who are likely to use t_try_realloc() */
+ i_assert(((unsigned char*)mem)[old_raw_size] == CLEAR_CHR);
+#endif
+
+ if (current_block->left >= alloc_growth) {
+ /* just shrink the available size */
+ current_block->left -= alloc_growth;
+ current_frame->last_alloc_size = new_alloc_size;
+#ifdef DEBUG
+ if (current_block->left < current_block->left_lowwater)
+ current_block->left_lowwater = current_block->left;
+ /* All reallocs are permanent by definition
+ However, they don't count as a new allocation */
+ current_frame->alloc_bytes += alloc_growth;
+ *(size_t *)PTR_OFFSET(mem, -(ptrdiff_t)MEM_ALIGN(sizeof(size_t))) = size;
+ memset(PTR_OFFSET(mem, size), CLEAR_CHR,
+ new_alloc_size - size - MEM_ALIGN(sizeof(size_t)));
+#endif
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+size_t t_get_bytes_available(void)
+{
+ block_canary_check(current_block);
+#ifndef DEBUG
+ const unsigned int min_extra = 0;
+#else
+ const unsigned int min_extra = SENTRY_COUNT + MEM_ALIGN(sizeof(size_t));
+ if (current_block->left < min_extra)
+ return 0;
+#endif
+ size_t size = current_block->left - min_extra;
+ i_assert(ALLOC_SIZE(size) == current_block->left);
+ return size;
+}
+
+void *t_buffer_get(size_t size)
+{
+ void *ret;
+
+ ret = t_malloc_real(size, FALSE);
+
+ last_buffer_size = size;
+ last_buffer_block = current_block;
+ return ret;
+}
+
+void *t_buffer_reget(void *buffer, size_t size)
+{
+ size_t old_size;
+ void *new_buffer;
+
+ old_size = last_buffer_size;
+ if (size <= old_size)
+ return buffer;
+
+ new_buffer = t_buffer_get(size);
+ if (new_buffer != buffer)
+ memcpy(new_buffer, buffer, old_size);
+
+ return new_buffer;
+}
+
+void t_buffer_alloc(size_t size)
+{
+ i_assert(last_buffer_block != NULL);
+ i_assert(last_buffer_size >= size);
+ i_assert(current_block->left >= size);
+
+ /* we've already reserved the space, now we just mark it used */
+ (void)t_malloc_real(size, TRUE);
+}
+
+void t_buffer_alloc_last_full(void)
+{
+ if (last_buffer_block != NULL)
+ (void)t_malloc_real(last_buffer_size, TRUE);
+}
+
+bool data_stack_frame_contains(data_stack_frame_t *id, const void *_ptr)
+{
+ const unsigned char *block_data, *ptr = _ptr;
+ const struct stack_block *block;
+ unsigned int wanted_frame_id;
+ size_t block_start_pos, block_used;
+
+ /* first handle the fast path - NULL can never be within the frame */
+ if (ptr == NULL)
+ return FALSE;
+
+#ifndef STATIC_CHECKER
+ wanted_frame_id = *id;
+#else
+ wanted_frame_id = (*id)->id;
+#endif
+ /* Too much effort to support more than the latest frame.
+ It's the only thing that is currently needed anyway. */
+ i_assert(wanted_frame_id+1 == data_stack_frame_id);
+ block = current_frame->block;
+ i_assert(block != NULL);
+
+ /* See if it's in the frame's first block. Only the data after
+ block_start_pos belong to this frame. */
+ block_data = STACK_BLOCK_DATA(block);
+ block_start_pos = block->size - current_frame->block_space_left;
+ block_used = block->size - block->left;
+ if (ptr >= block_data + block_start_pos &&
+ ptr <= block_data + block_used)
+ return TRUE;
+
+ /* See if it's in the other blocks. All the data in them belong to
+ this frame. */
+ for (block = block->next; block != NULL; block = block->next) {
+ block_data = STACK_BLOCK_DATA(block);
+ block_used = block->size - block->left;
+ if (ptr >= block_data && ptr < block_data + block_used)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+size_t data_stack_get_alloc_size(void)
+{
+ struct stack_block *block;
+ size_t size = 0;
+
+ i_assert(current_block->next == NULL);
+
+ for (block = current_block; block != NULL; block = block->prev)
+ size += block->size;
+ return size;
+}
+
+size_t data_stack_get_used_size(void)
+{
+ struct stack_block *block;
+ size_t size = 0;
+
+ i_assert(current_block->next == NULL);
+
+ for (block = current_block; block != NULL; block = block->prev)
+ size += block->size - block->left;
+ return size;
+}
+
+void data_stack_free_unused(void)
+{
+ free(unused_block);
+ unused_block = NULL;
+}
+
+void data_stack_init(void)
+{
+ if (data_stack_initialized) {
+ /* already initialized (we did auto-initialization in
+ t_malloc/t_push) */
+ return;
+ }
+ data_stack_initialized = TRUE;
+ data_stack_frame_id = 1;
+
+ outofmem_area.block.size = outofmem_area.block.left =
+ sizeof(outofmem_area) - sizeof(outofmem_area.block);
+ outofmem_area.block.canary = BLOCK_CANARY;
+
+ current_block = mem_block_alloc(INITIAL_STACK_SIZE);
+ current_frame = NULL;
+
+ last_buffer_block = NULL;
+ last_buffer_size = 0;
+
+ root_frame_id = t_push("data_stack_init");
+}
+
+void data_stack_deinit_event(void)
+{
+ event_unref(&event_datastack);
+ event_datastack_deinitialized = TRUE;
+}
+
+void data_stack_deinit(void)
+{
+ if (!t_pop(&root_frame_id) ||
+ current_frame != NULL)
+ i_panic("Missing t_pop() call");
+
+ free(current_block);
+ current_block = NULL;
+ data_stack_free_unused();
+}