Coverage Report

Created: 2026-05-30 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib/data-stack.c
Line
Count
Source
1
/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2
3
/* @UNSAFE: whole file */
4
5
#include "lib.h"
6
#include "backtrace-string.h"
7
#include "str.h"
8
#include "data-stack.h"
9
10
11
/* Initial stack size - this should be kept in a size that doesn't exceed
12
   in a normal use to avoid extra malloc()ing. */
13
#ifdef DEBUG
14
#  define INITIAL_STACK_SIZE (1024*10)
15
#else
16
0
#  define INITIAL_STACK_SIZE (1024*32)
17
#endif
18
19
#ifdef DEBUG
20
#  define CLEAR_CHR 0xD5               /* D5 is mnemonic for "Data 5tack" */
21
#  define SENTRY_COUNT (4*8)
22
#  define BLOCK_CANARY ((void *)0xBADBADD5BADBADD5)      /* contains 'D5' */
23
#  define ALLOC_SIZE(size) (MEM_ALIGN(sizeof(size_t)) + MEM_ALIGN(size + SENTRY_COUNT))
24
#else
25
#  define CLEAR_CHR 0
26
0
#  define BLOCK_CANARY NULL
27
0
#  define block_canary_check(block) do { ; } while(0)
28
0
#  define ALLOC_SIZE(size) MEM_ALIGN(size)
29
#endif
30
31
struct stack_block {
32
  struct stack_block *prev, *next;
33
34
  size_t size, left;
35
#ifdef DEBUG
36
  /* The lowest value that "left" has been in this block since it was
37
     last popped. This is used to keep track which parts of the block
38
     needs to be cleared if DEBUG is used. */
39
  size_t left_lowwater;
40
#endif
41
  /* NULL or a poison value, just in case something accesses
42
     the memory in front of an allocated area */
43
  void *canary;
44
  unsigned char data[FLEXIBLE_ARRAY_MEMBER];
45
};
46
47
0
#define SIZEOF_MEMBLOCK MEM_ALIGN(sizeof(struct stack_block))
48
49
#define STACK_BLOCK_DATA(block) \
50
0
  (block->data + (SIZEOF_MEMBLOCK - sizeof(struct stack_block)))
51
52
struct stack_frame {
53
  struct stack_frame *prev;
54
55
  struct stack_block *block;
56
  /* Each frame initializes this to current_block->left, i.e. how much
57
     free space is left in the block. So the frame's start position in
58
     the block is (block.size - block_space_left) */
59
  size_t block_space_left;
60
  size_t last_alloc_size;
61
  const char *marker;
62
#ifdef DEBUG
63
  /* Fairly arbitrary profiling data */
64
  unsigned long long alloc_bytes;
65
  unsigned int alloc_count;
66
#endif
67
};
68
69
#ifdef STATIC_CHECKER
70
struct data_stack_frame {
71
  unsigned int id;
72
};
73
#endif
74
75
unsigned int data_stack_frame_id = 0;
76
77
static bool data_stack_initialized = FALSE;
78
static data_stack_frame_t root_frame_id;
79
80
static struct stack_frame *current_frame;
81
82
/* The latest block currently used for allocation. current_block->next is
83
   always NULL. */
84
static struct stack_block *current_block;
85
/* The largest block that data stack has allocated so far, which was already
86
   freed. This can prevent rapid malloc()+free()ing when data stack is grown
87
   and shrunk constantly. */
88
static struct stack_block *unused_block = NULL;
89
90
static struct event *event_datastack = NULL;
91
static bool event_datastack_deinitialized = FALSE;
92
93
static struct stack_block *last_buffer_block;
94
static size_t last_buffer_size;
95
static bool outofmem = FALSE;
96
97
static union {
98
  struct stack_block block;
99
  unsigned char data[512];
100
} outofmem_area;
101
102
static struct stack_block *mem_block_alloc(size_t min_size);
103
104
static inline
105
unsigned char *data_stack_after_last_alloc(struct stack_block *block)
106
0
{
107
0
  return STACK_BLOCK_DATA(block) + (block->size - block->left);
108
0
}
109
110
static void data_stack_last_buffer_reset(bool preserve_data ATTR_UNUSED)
111
0
{
112
0
  if (last_buffer_block != NULL) {
113
#ifdef DEBUG
114
    unsigned char *last_alloc_end, *p, *pend;
115
116
    /* We assume that this function gets called before
117
       current_block changes. */
118
    i_assert(last_buffer_block == current_block);
119
120
    last_alloc_end = data_stack_after_last_alloc(current_block);
121
    p = last_alloc_end + MEM_ALIGN(sizeof(size_t)) + last_buffer_size;
122
    pend = last_alloc_end + ALLOC_SIZE(last_buffer_size);
123
#endif
124
    /* reset t_buffer_get() mark - not really needed but makes it
125
       easier to notice if t_malloc()/t_push()/t_pop() is called
126
       between t_buffer_get() and t_buffer_alloc().
127
       do this before we get to i_panic() to avoid recursive
128
       panics. */
129
0
    last_buffer_block = NULL;
130
131
#ifdef DEBUG
132
    /* NOTE: If the below panic triggers, it may also be due to an
133
       internal bug in data-stack (since this is rather complex). While
134
       debugging whether that is the case, it's a good idea to change the
135
       i_panic() to abort(). Otherwise the i_panic() changes the
136
       data-stack's internal state and complicates debugging. */
137
    while (p < pend)
138
      if (*p++ != CLEAR_CHR)
139
        i_panic("t_buffer_get(): buffer overflow");
140
141
    if (!preserve_data) {
142
      p = last_alloc_end;
143
      memset(p, CLEAR_CHR, SENTRY_COUNT);
144
    }
145
#endif
146
0
  }
147
0
}
148
149
data_stack_frame_t t_push(const char *marker)
150
0
{
151
0
  struct stack_frame *frame;
152
153
0
  i_assert(marker != NULL);
154
155
0
  if (unlikely(!data_stack_initialized)) {
156
    /* kludgy, but allow this before initialization */
157
0
    data_stack_init();
158
0
    return t_push(marker);
159
0
  }
160
161
  /* allocate new block */
162
0
  frame = t_buffer_get(sizeof(*frame));
163
0
  frame->prev = current_frame;
164
0
  current_frame = frame;
165
166
  /* mark our current position */
167
0
  current_frame->block = current_block;
168
0
  current_frame->block_space_left = current_block->left;
169
0
  current_frame->last_alloc_size = 0;
170
0
  current_frame->marker = marker;
171
#ifdef DEBUG
172
  current_frame->alloc_bytes = 0;
173
  current_frame->alloc_count = 0;
174
#endif
175
176
0
  t_buffer_alloc(sizeof(*frame));
177
178
0
#ifndef STATIC_CHECKER
179
0
  return data_stack_frame_id++;
180
#else
181
  struct data_stack_frame *ds_frame = i_new(struct data_stack_frame, 1);
182
  ds_frame->id = data_stack_frame_id++;
183
  return ds_frame;
184
#endif
185
0
}
186
187
data_stack_frame_t t_push_named(const char *format, ...)
188
0
{
189
0
  data_stack_frame_t ret = t_push(format);
190
#ifdef DEBUG
191
  va_list args;
192
  va_start(args, format);
193
  current_frame->marker = p_strdup_vprintf(unsafe_data_stack_pool, format, args);
194
  va_end(args);
195
#else
196
0
  (void)format; /* unused in non-DEBUG builds */
197
0
#endif
198
199
0
  return ret;
200
0
}
201
202
#ifdef DEBUG
203
static void block_canary_check(struct stack_block *block)
204
{
205
  if (block->canary != BLOCK_CANARY) {
206
    /* Make sure i_panic() won't try to allocate from the
207
       same block by falling back onto our emergency block. */
208
    current_block = &outofmem_area.block;
209
    i_panic("Corrupted data stack canary");
210
  }
211
}
212
#endif
213
214
static void free_blocks(struct stack_block *block)
215
0
{
216
0
  struct stack_block *next;
217
0
  int old_errno = errno;
218
219
  /* free all the blocks, except if any of them is bigger than
220
     unused_block, replace it */
221
0
  while (block != NULL) {
222
0
    block_canary_check(block);
223
0
    next = block->next;
224
225
#ifdef DEBUG
226
    memset(STACK_BLOCK_DATA(block), CLEAR_CHR, block->size);
227
#endif
228
229
0
    if (block == &outofmem_area.block)
230
0
      ;
231
0
    else if (unused_block == NULL ||
232
0
       block->size > unused_block->size) {
233
0
      free(unused_block);
234
0
      unused_block = block;
235
0
    } else {
236
0
      free(block);
237
0
    }
238
239
0
    block = next;
240
0
  }
241
0
  errno = old_errno;
242
0
}
243
244
#ifdef DEBUG
245
static void t_pop_verify(void)
246
{
247
  struct stack_block *block;
248
  unsigned char *p;
249
  size_t pos, max_pos, used_size;
250
251
  block = current_frame->block;
252
  pos = block->size - current_frame->block_space_left;
253
  while (block != NULL) {
254
    block_canary_check(block);
255
    used_size = block->size - block->left;
256
    p = STACK_BLOCK_DATA(block);
257
    while (pos < used_size) {
258
      size_t requested_size = *(size_t *)(p + pos);
259
      if (used_size - pos < requested_size)
260
        i_panic("data stack[%s]: saved alloc size broken",
261
          current_frame->marker);
262
      max_pos = pos + ALLOC_SIZE(requested_size);
263
      pos += MEM_ALIGN(sizeof(size_t)) + requested_size;
264
265
      for (; pos < max_pos; pos++) {
266
        if (p[pos] != CLEAR_CHR)
267
          i_panic("data stack[%s]: buffer overflow",
268
            current_frame->marker);
269
      }
270
    }
271
272
    /* if we had used t_buffer_get(), the rest of the buffer
273
       may not contain CLEAR_CHRs. but we've already checked all
274
       the allocations, so there's no need to check them anyway. */
275
    block = block->next;
276
    pos = 0;
277
  }
278
}
279
#endif
280
281
void t_pop_last_unsafe(void)
282
0
{
283
0
  size_t block_space_left;
284
285
0
  if (unlikely(current_frame == NULL))
286
0
    i_panic("t_pop() called with empty stack");
287
288
0
  data_stack_last_buffer_reset(FALSE);
289
#ifdef DEBUG
290
  t_pop_verify();
291
#endif
292
293
  /* Usually the block doesn't change. If it doesn't, the next pointer
294
     must also be NULL. */
295
0
  if (current_block != current_frame->block) {
296
0
    current_block = current_frame->block;
297
0
    if (current_block->next != NULL) {
298
      /* free unused blocks */
299
0
      free_blocks(current_block->next);
300
0
      current_block->next = NULL;
301
0
    }
302
0
  }
303
0
  block_canary_check(current_block);
304
305
  /* current_frame points inside the stack frame that will be freed.
306
     make sure it's not accessed after it's already freed/cleaned. */
307
0
  block_space_left = current_frame->block_space_left;
308
0
  current_frame = current_frame->prev;
309
310
#ifdef DEBUG
311
  size_t start_pos, end_pos;
312
313
  start_pos = current_block->size - block_space_left;
314
  end_pos = current_block->size - current_block->left_lowwater;
315
  i_assert(end_pos >= start_pos);
316
  memset(STACK_BLOCK_DATA(current_block) + start_pos, CLEAR_CHR,
317
         end_pos - start_pos);
318
  current_block->left_lowwater = block_space_left;
319
#endif
320
321
0
  current_block->left = block_space_left;
322
323
0
  data_stack_frame_id--;
324
0
}
325
326
bool t_pop(data_stack_frame_t *id)
327
0
{
328
0
  t_pop_last_unsafe();
329
0
#ifndef STATIC_CHECKER
330
0
  if (unlikely(data_stack_frame_id != *id))
331
0
    return FALSE;
332
0
  *id = 0;
333
#else
334
  unsigned int frame_id = (*id)->id;
335
  i_free_and_null(*id);
336
337
  if (unlikely(data_stack_frame_id != frame_id))
338
    return FALSE;
339
#endif
340
0
  return TRUE;
341
0
}
342
343
bool t_pop_pass_str(data_stack_frame_t *id, const char **str)
344
0
{
345
0
  if (str == NULL || !data_stack_frame_contains(id, *str))
346
0
    return t_pop(id);
347
348
  /* FIXME: The string could be memmove()d to the beginning of the
349
     data stack frame and the previous frame's size extended past it.
350
     This would avoid the malloc. It's a bit complicated though. */
351
0
  char *tmp_str = i_strdup(*str);
352
0
  bool ret = t_pop(id);
353
0
  *str = t_strdup(tmp_str);
354
0
  i_free(tmp_str);
355
0
  return ret;
356
0
}
357
358
static void mem_block_reset(struct stack_block *block)
359
0
{
360
0
  block->prev = NULL;
361
0
  block->next = NULL;
362
0
  block->left = block->size;
363
#ifdef DEBUG
364
  block->left_lowwater = block->size;
365
#endif
366
0
}
367
368
static struct stack_block *mem_block_alloc(size_t min_size)
369
0
{
370
0
  struct stack_block *block;
371
0
  size_t prev_size, alloc_size;
372
0
  int old_errno = errno;
373
374
0
  prev_size = current_block == NULL ? 0 : current_block->size;
375
  /* Use INITIAL_STACK_SIZE without growing it to nearest power. */
376
0
  alloc_size = prev_size == 0 ? min_size :
377
0
    nearest_power(MALLOC_ADD(prev_size, min_size));
378
379
  /* nearest_power() returns 2^n values, so alloc_size can't be
380
     anywhere close to SIZE_MAX */
381
0
  block = malloc(MALLOC_ADD(SIZEOF_MEMBLOCK, alloc_size));
382
0
  if (unlikely(block == NULL)) {
383
0
    if (outofmem) {
384
0
      if (min_size > outofmem_area.block.left)
385
0
        abort();
386
0
      return &outofmem_area.block;
387
0
    }
388
0
    outofmem = TRUE;
389
0
    i_panic("data stack: Out of memory when allocating %zu bytes",
390
0
      alloc_size + SIZEOF_MEMBLOCK);
391
0
  }
392
0
  errno = old_errno;
393
0
  block->size = alloc_size;
394
0
  block->canary = BLOCK_CANARY;
395
0
  mem_block_reset(block);
396
#ifdef DEBUG
397
  memset(STACK_BLOCK_DATA(block), CLEAR_CHR, alloc_size);
398
#endif
399
0
  return block;
400
0
}
401
402
static void data_stack_send_grow_event(size_t last_alloc_size)
403
0
{
404
  /* The t_malloc_real() adds a data stack frame. We don't care about it,
405
     but the previous one. */
406
0
  struct stack_frame *frame = current_frame->prev;
407
408
0
  if (event_datastack_deinitialized) {
409
    /* already in the deinitialization code -
410
       don't send more events */
411
0
    return;
412
0
  }
413
0
  if (event_datastack == NULL)
414
0
    event_datastack = event_create(NULL);
415
0
  event_set_name(event_datastack, "data_stack_grow");
416
0
  event_add_int(event_datastack, "alloc_size", data_stack_get_alloc_size());
417
0
  event_add_int(event_datastack, "used_size", data_stack_get_used_size());
418
0
  event_add_int(event_datastack, "last_alloc_size", last_alloc_size);
419
0
  event_add_int(event_datastack, "last_block_size", current_block->size);
420
#ifdef DEBUG
421
  event_add_int(event_datastack, "frame_alloc_bytes",
422
          frame->alloc_bytes);
423
  event_add_int(event_datastack, "frame_alloc_count",
424
          frame->alloc_count);
425
#endif
426
0
  event_add_str(event_datastack, "frame_marker", frame->marker);
427
428
  /* It's possible that the data stack gets grown and shrunk rapidly.
429
     Try to avoid doing expensive work if the event isn't even used for
430
     anything. Note that at this point all the event fields must be
431
     set already that might potentially be used by the filters. */
432
0
  if (!event_want_debug(event_datastack))
433
0
    return;
434
435
  /* Getting backtrace is potentially inefficient, so do it after
436
     checking if the event is wanted. Note that this prevents using the
437
     backtrace field in event field comparisons. */
438
0
  const char *backtrace, *error;
439
0
  if (backtrace_get(&backtrace, &error) == 0)
440
0
    event_add_str(event_datastack, "backtrace", backtrace);
441
0
  else {
442
0
    backtrace = t_strdup_printf("backtrace failed: %s", error);
443
0
    event_add_str(event_datastack, "backtrace_error", error);
444
0
  }
445
446
0
  string_t *str = t_str_new(128);
447
0
  str_printfa(str, "total_used=%zu, total_alloc=%zu, last_alloc_size=%zu",
448
0
        data_stack_get_used_size(),
449
0
        data_stack_get_alloc_size(),
450
0
        last_alloc_size);
451
#ifdef DEBUG
452
  str_printfa(str, ", frame_bytes=%llu, frame_alloc_count=%u",
453
        frame->alloc_bytes, frame->alloc_count);
454
#endif
455
0
  e_debug(event_datastack, "Growing data stack by %zu for '%s' (%s): %s",
456
0
    current_block->size, frame->marker, str_c(str), backtrace);
457
0
}
458
459
static void *t_malloc_real(size_t size, bool permanent)
460
0
{
461
0
  void *ret;
462
0
  size_t alloc_size;
463
0
  bool warn = FALSE;
464
0
  int old_errno = errno;
465
466
0
  if (unlikely(size == 0 || size > SSIZE_T_MAX))
467
0
    i_panic("Trying to allocate %zu bytes", size);
468
469
0
  if (unlikely(!data_stack_initialized)) {
470
    /* kludgy, but allow this before initialization */
471
0
    data_stack_init();
472
0
  }
473
0
  block_canary_check(current_block);
474
475
  /* allocate only aligned amount of memory so alignment comes
476
     always properly */
477
0
  alloc_size = ALLOC_SIZE(size);
478
#ifdef DEBUG
479
  if(permanent) {
480
    current_frame->alloc_bytes += alloc_size;
481
    current_frame->alloc_count++;
482
  }
483
#endif
484
0
  data_stack_last_buffer_reset(TRUE);
485
486
0
  if (permanent) {
487
    /* used for t_try_realloc() */
488
0
    current_frame->last_alloc_size = alloc_size;
489
0
  }
490
491
0
  if (current_block->left < alloc_size) {
492
0
    struct stack_block *block;
493
494
    /* current block is full, see if we can use the unused_block */
495
0
    if (unused_block != NULL && unused_block->size >= alloc_size) {
496
0
      block = unused_block;
497
0
      unused_block = NULL;
498
0
      mem_block_reset(block);
499
0
    } else {
500
      /* current block is full, allocate a new one */
501
0
      block = mem_block_alloc(alloc_size);
502
0
      warn = TRUE;
503
0
    }
504
505
    /* The newly allocated block will replace the current_block,
506
       i.e. current_block always points to the last element in
507
       the linked list. */
508
0
    block->prev = current_block;
509
0
    current_block->next = block;
510
0
    current_block = block;
511
0
  }
512
513
  /* enough space in current block, use it */
514
0
  ret = data_stack_after_last_alloc(current_block);
515
516
#ifdef DEBUG
517
  if (current_block->left - alloc_size < current_block->left_lowwater)
518
    current_block->left_lowwater = current_block->left - alloc_size;
519
#endif
520
0
  if (permanent)
521
0
    current_block->left -= alloc_size;
522
523
0
  if (warn) T_BEGIN {
524
    /* warn after allocation, so if e_debug() wants to
525
       allocate more memory we don't go to infinite loop */
526
0
    data_stack_send_grow_event(alloc_size);
527
0
  } T_END;
528
#ifdef DEBUG
529
  memcpy(ret, &size, sizeof(size));
530
  ret = PTR_OFFSET(ret, MEM_ALIGN(sizeof(size)));
531
  /* make sure the sentry contains CLEAR_CHRs. it might not if we
532
     had used t_buffer_get(). */
533
  memset(PTR_OFFSET(ret, size), CLEAR_CHR,
534
         MEM_ALIGN(size + SENTRY_COUNT) - size);
535
#endif
536
0
  errno = old_errno;
537
0
  return ret;
538
0
}
539
540
void *t_malloc_no0(size_t size)
541
0
{
542
0
  return t_malloc_real(size, TRUE);
543
0
}
544
545
void *t_malloc0(size_t size)
546
0
{
547
0
  void *mem;
548
549
0
  mem = t_malloc_real(size, TRUE);
550
0
  memset(mem, 0, size);
551
0
  return mem;
552
0
}
553
554
bool ATTR_NO_SANITIZE_INTEGER
555
t_try_realloc(void *mem, size_t size)
556
0
{
557
0
  size_t debug_adjust = 0, last_alloc_size;
558
0
  unsigned char *after_last_alloc;
559
560
0
  if (unlikely(size == 0 || size > SSIZE_T_MAX))
561
0
    i_panic("Trying to allocate %zu bytes", size);
562
0
  block_canary_check(current_block);
563
0
  data_stack_last_buffer_reset(TRUE);
564
565
0
  last_alloc_size = current_frame->last_alloc_size;
566
567
  /* see if we're trying to grow the memory we allocated last */
568
0
  after_last_alloc = data_stack_after_last_alloc(current_block);
569
#ifdef DEBUG
570
  debug_adjust = MEM_ALIGN(sizeof(size_t));
571
#endif
572
0
  if (after_last_alloc - last_alloc_size + debug_adjust == mem) {
573
    /* yeah, see if we have space to grow */
574
0
    size_t new_alloc_size, alloc_growth;
575
576
0
    new_alloc_size = ALLOC_SIZE(size);
577
0
    alloc_growth = (new_alloc_size - last_alloc_size);
578
#ifdef DEBUG
579
    size_t old_raw_size; /* sorry, non-C99 users - add braces if you need them */
580
    old_raw_size = *(size_t *)PTR_OFFSET(mem, -(ptrdiff_t)MEM_ALIGN(sizeof(size_t)));
581
    i_assert(ALLOC_SIZE(old_raw_size) == last_alloc_size);
582
    /* Only check one byte for over-run, that catches most
583
       offenders who are likely to use t_try_realloc() */
584
    i_assert(((unsigned char*)mem)[old_raw_size] == CLEAR_CHR);
585
#endif
586
587
0
    if (current_block->left >= alloc_growth) {
588
      /* just shrink the available size */
589
0
      current_block->left -= alloc_growth;
590
0
      current_frame->last_alloc_size = new_alloc_size;
591
#ifdef DEBUG
592
      if (current_block->left < current_block->left_lowwater)
593
        current_block->left_lowwater = current_block->left;
594
      /* All reallocs are permanent by definition
595
         However, they don't count as a new allocation */
596
      current_frame->alloc_bytes += alloc_growth;
597
      *(size_t *)PTR_OFFSET(mem, -(ptrdiff_t)MEM_ALIGN(sizeof(size_t))) = size;
598
      memset(PTR_OFFSET(mem, size), CLEAR_CHR,
599
             new_alloc_size - size - MEM_ALIGN(sizeof(size_t)));
600
#endif
601
0
      return TRUE;
602
0
    }
603
0
  }
604
605
0
  return FALSE;
606
0
}
607
608
size_t t_get_bytes_available(void)
609
0
{
610
0
  block_canary_check(current_block);
611
0
#ifndef DEBUG
612
0
  const unsigned int min_extra = 0;
613
#else
614
  const unsigned int min_extra = SENTRY_COUNT + MEM_ALIGN(sizeof(size_t));
615
  if (current_block->left < min_extra)
616
    return 0;
617
#endif
618
0
  size_t size = current_block->left - min_extra;
619
0
  i_assert(ALLOC_SIZE(size) == current_block->left);
620
0
  return size;
621
0
}
622
623
void *t_buffer_get(size_t size)
624
0
{
625
0
  void *ret;
626
627
0
  ret = t_malloc_real(size, FALSE);
628
629
0
  last_buffer_size = size;
630
0
  last_buffer_block = current_block;
631
0
  return ret;
632
0
}
633
634
void *t_buffer_reget(void *buffer, size_t size)
635
0
{
636
0
  size_t old_size;
637
0
  void *new_buffer;
638
639
0
  old_size = last_buffer_size;
640
0
  if (size <= old_size)
641
0
    return buffer;
642
643
0
  new_buffer = t_buffer_get(size);
644
0
  if (new_buffer != buffer)
645
0
    memcpy(new_buffer, buffer, old_size);
646
647
0
  return new_buffer;
648
0
}
649
650
void t_buffer_alloc(size_t size)
651
0
{
652
0
  i_assert(last_buffer_block != NULL);
653
0
  i_assert(last_buffer_size >= size);
654
0
  i_assert(current_block->left >= size);
655
656
  /* we've already reserved the space, now we just mark it used */
657
0
  (void)t_malloc_real(size, TRUE);
658
0
}
659
660
void t_buffer_alloc_last_full(void)
661
0
{
662
0
  if (last_buffer_block != NULL)
663
0
    (void)t_malloc_real(last_buffer_size, TRUE);
664
0
}
665
666
bool data_stack_frame_contains(data_stack_frame_t *id, const void *_ptr)
667
0
{
668
0
  const unsigned char *block_data, *ptr = _ptr;
669
0
  const struct stack_block *block;
670
0
  unsigned int wanted_frame_id;
671
0
  size_t block_start_pos, block_used;
672
673
  /* first handle the fast path - NULL can never be within the frame */
674
0
  if (ptr == NULL)
675
0
    return FALSE;
676
677
0
#ifndef STATIC_CHECKER
678
0
  wanted_frame_id = *id;
679
#else
680
  wanted_frame_id = (*id)->id;
681
#endif
682
  /* Too much effort to support more than the latest frame.
683
     It's the only thing that is currently needed anyway. */
684
0
  i_assert(wanted_frame_id+1 == data_stack_frame_id);
685
0
  block = current_frame->block;
686
0
  i_assert(block != NULL);
687
688
  /* See if it's in the frame's first block. Only the data after
689
     block_start_pos belong to this frame. */
690
0
  block_data = STACK_BLOCK_DATA(block);
691
0
  block_start_pos = block->size - current_frame->block_space_left;
692
0
  block_used = block->size - block->left;
693
0
  if (ptr >= block_data + block_start_pos &&
694
0
      ptr <= block_data + block_used)
695
0
    return TRUE;
696
697
  /* See if it's in the other blocks. All the data in them belong to
698
     this frame. */
699
0
  for (block = block->next; block != NULL; block = block->next) {
700
0
    block_data = STACK_BLOCK_DATA(block);
701
0
    block_used = block->size - block->left;
702
0
    if (ptr >= block_data && ptr < block_data + block_used)
703
0
      return TRUE;
704
0
  }
705
0
  return FALSE;
706
0
}
707
708
size_t data_stack_get_alloc_size(void)
709
0
{
710
0
  struct stack_block *block;
711
0
  size_t size = 0;
712
713
0
  i_assert(current_block->next == NULL);
714
715
0
  for (block = current_block; block != NULL; block = block->prev)
716
0
    size += block->size;
717
0
  return size;
718
0
}
719
720
size_t data_stack_get_used_size(void)
721
0
{
722
0
  struct stack_block *block;
723
0
  size_t size = 0;
724
725
0
  i_assert(current_block->next == NULL);
726
727
0
  for (block = current_block; block != NULL; block = block->prev)
728
0
    size += block->size - block->left;
729
0
  return size;
730
0
}
731
732
void data_stack_free_unused(void)
733
0
{
734
0
  int old_errno = errno;
735
0
  free(unused_block);
736
0
  unused_block = NULL;
737
0
  errno = old_errno;
738
0
}
739
740
void data_stack_init(void)
741
0
{
742
0
  if (data_stack_initialized) {
743
    /* already initialized (we did auto-initialization in
744
       t_malloc/t_push) */
745
0
    return;
746
0
  }
747
0
  data_stack_initialized = TRUE;
748
0
  data_stack_frame_id = 1;
749
750
0
  outofmem_area.block.size = outofmem_area.block.left =
751
0
    sizeof(outofmem_area) - sizeof(outofmem_area.block);
752
0
  outofmem_area.block.canary = BLOCK_CANARY;
753
0
  outofmem = FALSE;
754
755
0
  unused_block = NULL;
756
0
  current_block = mem_block_alloc(INITIAL_STACK_SIZE);
757
0
  current_frame = NULL;
758
759
0
  last_buffer_block = NULL;
760
0
  last_buffer_size = 0;
761
762
0
  root_frame_id = t_push("data_stack_init");
763
764
0
  event_datastack = NULL;
765
0
  event_datastack_deinitialized = FALSE;
766
0
}
767
768
void data_stack_deinit_event(void)
769
0
{
770
0
  event_unref(&event_datastack);
771
0
  event_datastack_deinitialized = TRUE;
772
0
}
773
774
void data_stack_deinit(void)
775
0
{
776
0
  if (!t_pop(&root_frame_id) ||
777
0
      current_frame != NULL)
778
0
    i_panic("Missing t_pop() call");
779
780
0
  free(current_block);
781
0
  current_block = NULL;
782
0
  data_stack_free_unused();
783
0
  data_stack_initialized = FALSE;
784
0
}