Coverage Report

Created: 2026-06-15 06:36

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
2.55k
#  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
5.17k
#  define BLOCK_CANARY NULL
27
372k
#  define block_canary_check(block) do { ; } while(0)
28
364k
#  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
366k
#define SIZEOF_MEMBLOCK MEM_ALIGN(sizeof(struct stack_block))
48
49
#define STACK_BLOCK_DATA(block) \
50
366k
  (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
364k
{
107
364k
  return STACK_BLOCK_DATA(block) + (block->size - block->left);
108
364k
}
109
110
static void data_stack_last_buffer_reset(bool preserve_data ATTR_UNUSED)
111
372k
{
112
372k
  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
343k
    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
343k
  }
147
372k
}
148
149
data_stack_frame_t t_push(const char *marker)
150
7.62k
{
151
7.62k
  struct stack_frame *frame;
152
153
7.62k
  i_assert(marker != NULL);
154
155
7.62k
  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
7.62k
  frame = t_buffer_get(sizeof(*frame));
163
7.62k
  frame->prev = current_frame;
164
7.62k
  current_frame = frame;
165
166
  /* mark our current position */
167
7.62k
  current_frame->block = current_block;
168
7.62k
  current_frame->block_space_left = current_block->left;
169
7.62k
  current_frame->last_alloc_size = 0;
170
7.62k
  current_frame->marker = marker;
171
#ifdef DEBUG
172
  current_frame->alloc_bytes = 0;
173
  current_frame->alloc_count = 0;
174
#endif
175
176
7.62k
  t_buffer_alloc(sizeof(*frame));
177
178
7.62k
#ifndef STATIC_CHECKER
179
7.62k
  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
7.62k
}
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
74
{
216
74
  struct stack_block *next;
217
74
  int old_errno = errno;
218
219
  /* free all the blocks, except if any of them is bigger than
220
     unused_block, replace it */
221
148
  while (block != NULL) {
222
74
    block_canary_check(block);
223
74
    next = block->next;
224
225
#ifdef DEBUG
226
    memset(STACK_BLOCK_DATA(block), CLEAR_CHR, block->size);
227
#endif
228
229
74
    if (block == &outofmem_area.block)
230
0
      ;
231
74
    else if (unused_block == NULL ||
232
74
       block->size > unused_block->size) {
233
74
      free(unused_block);
234
74
      unused_block = block;
235
74
    } else {
236
0
      free(block);
237
0
    }
238
239
74
    block = next;
240
74
  }
241
74
  errno = old_errno;
242
74
}
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
7.62k
{
283
7.62k
  size_t block_space_left;
284
285
7.62k
  if (unlikely(current_frame == NULL))
286
0
    i_panic("t_pop() called with empty stack");
287
288
7.62k
  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
7.62k
  if (current_block != current_frame->block) {
296
74
    current_block = current_frame->block;
297
74
    if (current_block->next != NULL) {
298
      /* free unused blocks */
299
74
      free_blocks(current_block->next);
300
74
      current_block->next = NULL;
301
74
    }
302
74
  }
303
7.62k
  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
7.62k
  block_space_left = current_frame->block_space_left;
308
7.62k
  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
7.62k
  current_block->left = block_space_left;
322
323
7.62k
  data_stack_frame_id--;
324
7.62k
}
325
326
bool t_pop(data_stack_frame_t *id)
327
7.62k
{
328
7.62k
  t_pop_last_unsafe();
329
7.62k
#ifndef STATIC_CHECKER
330
7.62k
  if (unlikely(data_stack_frame_id != *id))
331
0
    return FALSE;
332
7.62k
  *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
7.62k
  return TRUE;
341
7.62k
}
342
343
bool t_pop_pass_str(data_stack_frame_t *id, const char **str)
344
2.45k
{
345
2.45k
  if (str == NULL || !data_stack_frame_contains(id, *str))
346
355
    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
2.09k
  char *tmp_str = i_strdup(*str);
352
2.09k
  bool ret = t_pop(id);
353
2.09k
  *str = t_strdup(tmp_str);
354
2.09k
  i_free(tmp_str);
355
2.09k
  return ret;
356
2.45k
}
357
358
static void mem_block_reset(struct stack_block *block)
359
2.62k
{
360
2.62k
  block->prev = NULL;
361
2.62k
  block->next = NULL;
362
2.62k
  block->left = block->size;
363
#ifdef DEBUG
364
  block->left_lowwater = block->size;
365
#endif
366
2.62k
}
367
368
static struct stack_block *mem_block_alloc(size_t min_size)
369
2.62k
{
370
2.62k
  struct stack_block *block;
371
2.62k
  size_t prev_size, alloc_size;
372
2.62k
  int old_errno = errno;
373
374
2.62k
  prev_size = current_block == NULL ? 0 : current_block->size;
375
  /* Use INITIAL_STACK_SIZE without growing it to nearest power. */
376
2.62k
  alloc_size = prev_size == 0 ? min_size :
377
2.62k
    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
2.62k
  block = malloc(MALLOC_ADD(SIZEOF_MEMBLOCK, alloc_size));
382
2.62k
  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
2.62k
  errno = old_errno;
393
2.62k
  block->size = alloc_size;
394
2.62k
  block->canary = BLOCK_CANARY;
395
2.62k
  mem_block_reset(block);
396
#ifdef DEBUG
397
  memset(STACK_BLOCK_DATA(block), CLEAR_CHR, alloc_size);
398
#endif
399
2.62k
  return block;
400
2.62k
}
401
402
static void data_stack_send_grow_event(size_t last_alloc_size)
403
74
{
404
  /* The t_malloc_real() adds a data stack frame. We don't care about it,
405
     but the previous one. */
406
74
  struct stack_frame *frame = current_frame->prev;
407
408
74
  if (event_datastack_deinitialized) {
409
    /* already in the deinitialization code -
410
       don't send more events */
411
0
    return;
412
0
  }
413
74
  if (event_datastack == NULL)
414
74
    event_datastack = event_create(NULL);
415
74
  event_set_name(event_datastack, "data_stack_grow");
416
74
  event_add_int(event_datastack, "alloc_size", data_stack_get_alloc_size());
417
74
  event_add_int(event_datastack, "used_size", data_stack_get_used_size());
418
74
  event_add_int(event_datastack, "last_alloc_size", last_alloc_size);
419
74
  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
74
  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
74
  if (!event_want_debug(event_datastack))
433
74
    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
364k
{
461
364k
  void *ret;
462
364k
  size_t alloc_size;
463
364k
  bool warn = FALSE;
464
364k
  int old_errno = errno;
465
466
364k
  if (unlikely(size == 0 || size > SSIZE_T_MAX))
467
0
    i_panic("Trying to allocate %zu bytes", size);
468
469
364k
  if (unlikely(!data_stack_initialized)) {
470
    /* kludgy, but allow this before initialization */
471
0
    data_stack_init();
472
0
  }
473
364k
  block_canary_check(current_block);
474
475
  /* allocate only aligned amount of memory so alignment comes
476
     always properly */
477
364k
  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
364k
  data_stack_last_buffer_reset(TRUE);
485
486
364k
  if (permanent) {
487
    /* used for t_try_realloc() */
488
20.9k
    current_frame->last_alloc_size = alloc_size;
489
20.9k
  }
490
491
364k
  if (current_block->left < alloc_size) {
492
74
    struct stack_block *block;
493
494
    /* current block is full, see if we can use the unused_block */
495
74
    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
74
    } else {
500
      /* current block is full, allocate a new one */
501
74
      block = mem_block_alloc(alloc_size);
502
74
      warn = TRUE;
503
74
    }
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
74
    block->prev = current_block;
509
74
    current_block->next = block;
510
74
    current_block = block;
511
74
  }
512
513
  /* enough space in current block, use it */
514
364k
  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
364k
  if (permanent)
521
20.9k
    current_block->left -= alloc_size;
522
523
364k
  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
148
    data_stack_send_grow_event(alloc_size);
527
148
  } 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
364k
  errno = old_errno;
537
364k
  return ret;
538
364k
}
539
540
void *t_malloc_no0(size_t size)
541
13.1k
{
542
13.1k
  return t_malloc_real(size, TRUE);
543
13.1k
}
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
343k
{
625
343k
  void *ret;
626
627
343k
  ret = t_malloc_real(size, FALSE);
628
629
343k
  last_buffer_size = size;
630
343k
  last_buffer_block = current_block;
631
343k
  return ret;
632
343k
}
633
634
void *t_buffer_reget(void *buffer, size_t size)
635
142k
{
636
142k
  size_t old_size;
637
142k
  void *new_buffer;
638
639
142k
  old_size = last_buffer_size;
640
142k
  if (size <= old_size)
641
0
    return buffer;
642
643
142k
  new_buffer = t_buffer_get(size);
644
142k
  if (new_buffer != buffer)
645
0
    memcpy(new_buffer, buffer, old_size);
646
647
142k
  return new_buffer;
648
142k
}
649
650
void t_buffer_alloc(size_t size)
651
7.73k
{
652
7.73k
  i_assert(last_buffer_block != NULL);
653
7.73k
  i_assert(last_buffer_size >= size);
654
7.73k
  i_assert(current_block->left >= size);
655
656
  /* we've already reserved the space, now we just mark it used */
657
7.73k
  (void)t_malloc_real(size, TRUE);
658
7.73k
}
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
2.09k
{
668
2.09k
  const unsigned char *block_data, *ptr = _ptr;
669
2.09k
  const struct stack_block *block;
670
2.09k
  unsigned int wanted_frame_id;
671
2.09k
  size_t block_start_pos, block_used;
672
673
  /* first handle the fast path - NULL can never be within the frame */
674
2.09k
  if (ptr == NULL)
675
0
    return FALSE;
676
677
2.09k
#ifndef STATIC_CHECKER
678
2.09k
  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
2.09k
  i_assert(wanted_frame_id+1 == data_stack_frame_id);
685
2.09k
  block = current_frame->block;
686
2.09k
  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
2.09k
  block_data = STACK_BLOCK_DATA(block);
691
2.09k
  block_start_pos = block->size - current_frame->block_space_left;
692
2.09k
  block_used = block->size - block->left;
693
2.09k
  if (ptr >= block_data + block_start_pos &&
694
2.09k
      ptr <= block_data + block_used)
695
2.09k
    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
74
{
710
74
  struct stack_block *block;
711
74
  size_t size = 0;
712
713
74
  i_assert(current_block->next == NULL);
714
715
222
  for (block = current_block; block != NULL; block = block->prev)
716
148
    size += block->size;
717
74
  return size;
718
74
}
719
720
size_t data_stack_get_used_size(void)
721
74
{
722
74
  struct stack_block *block;
723
74
  size_t size = 0;
724
725
74
  i_assert(current_block->next == NULL);
726
727
222
  for (block = current_block; block != NULL; block = block->prev)
728
148
    size += block->size - block->left;
729
74
  return size;
730
74
}
731
732
void data_stack_free_unused(void)
733
2.55k
{
734
2.55k
  int old_errno = errno;
735
2.55k
  free(unused_block);
736
2.55k
  unused_block = NULL;
737
2.55k
  errno = old_errno;
738
2.55k
}
739
740
void data_stack_init(void)
741
2.55k
{
742
2.55k
  if (data_stack_initialized) {
743
    /* already initialized (we did auto-initialization in
744
       t_malloc/t_push) */
745
0
    return;
746
0
  }
747
2.55k
  data_stack_initialized = TRUE;
748
2.55k
  data_stack_frame_id = 1;
749
750
2.55k
  outofmem_area.block.size = outofmem_area.block.left =
751
2.55k
    sizeof(outofmem_area) - sizeof(outofmem_area.block);
752
2.55k
  outofmem_area.block.canary = BLOCK_CANARY;
753
2.55k
  outofmem = FALSE;
754
755
2.55k
  unused_block = NULL;
756
2.55k
  current_block = mem_block_alloc(INITIAL_STACK_SIZE);
757
2.55k
  current_frame = NULL;
758
759
2.55k
  last_buffer_block = NULL;
760
2.55k
  last_buffer_size = 0;
761
762
2.55k
  root_frame_id = t_push("data_stack_init");
763
764
2.55k
  event_datastack = NULL;
765
2.55k
  event_datastack_deinitialized = FALSE;
766
2.55k
}
767
768
void data_stack_deinit_event(void)
769
2.55k
{
770
2.55k
  event_unref(&event_datastack);
771
2.55k
  event_datastack_deinitialized = TRUE;
772
2.55k
}
773
774
void data_stack_deinit(void)
775
2.55k
{
776
2.55k
  if (!t_pop(&root_frame_id) ||
777
2.55k
      current_frame != NULL)
778
0
    i_panic("Missing t_pop() call");
779
780
2.55k
  free(current_block);
781
2.55k
  current_block = NULL;
782
2.55k
  data_stack_free_unused();
783
2.55k
  data_stack_initialized = FALSE;
784
2.55k
}