1 /*
2 * Copyright (c) 2000-2004 Proofpoint, Inc. and its suppliers.
3 * All rights reserved.
4 *
5 * By using this file, you agree to the terms and conditions set
6 * forth in the LICENSE file which can be found at the top level of
7 * the sendmail distribution.
8 */
9
10 #include <sm/gen.h>
11 SM_RCSID("@(#)$Id: rpool.c,v 1.29 2013-11-22 20:51:43 ca Exp $")
12
13 /*
14 ** resource pools
15 ** For documentation, see rpool.html
16 */
17
18 #include <sm/exc.h>
19 #include <sm/heap.h>
20 #include <sm/rpool.h>
21 #include <sm/varargs.h>
22 #include <sm/conf.h>
23 #if _FFR_PERF_RPOOL
24 # include <syslog.h>
25 #endif
26
27 const char SmRpoolMagic[] = "sm_rpool";
28
29 typedef union
30 {
31 SM_POOLLINK_T link;
32 char align[SM_ALIGN_SIZE];
33 } SM_POOLHDR_T;
34
35 static char *sm_rpool_allocblock_x __P((SM_RPOOL_T *, size_t));
36 static char *sm_rpool_allocblock __P((SM_RPOOL_T *, size_t));
37
38 /*
39 ** Tune this later
40 */
41
42 #define POOLSIZE 4096
43 #define BIG_OBJECT_RATIO 10
44
45 /*
46 ** SM_RPOOL_ALLOCBLOCK_X -- allocate a new block for an rpool.
47 **
48 ** Parameters:
49 ** rpool -- rpool to which the block should be added.
50 ** size -- size of block.
51 **
52 ** Returns:
53 ** Pointer to block.
54 **
55 ** Exceptions:
56 ** F:sm_heap -- out of memory
57 */
58
59 static char *
sm_rpool_allocblock_x(rpool,size)60 sm_rpool_allocblock_x(rpool, size)
61 SM_RPOOL_T *rpool;
62 size_t size;
63 {
64 SM_POOLLINK_T *p;
65
66 p = sm_malloc_x(sizeof(SM_POOLHDR_T) + size);
67 p->sm_pnext = rpool->sm_pools;
68 rpool->sm_pools = p;
69 return (char*) p + sizeof(SM_POOLHDR_T);
70 }
71
72 /*
73 ** SM_RPOOL_ALLOCBLOCK -- allocate a new block for an rpool.
74 **
75 ** Parameters:
76 ** rpool -- rpool to which the block should be added.
77 ** size -- size of block.
78 **
79 ** Returns:
80 ** Pointer to block, NULL on failure.
81 */
82
83 static char *
sm_rpool_allocblock(rpool,size)84 sm_rpool_allocblock(rpool, size)
85 SM_RPOOL_T *rpool;
86 size_t size;
87 {
88 SM_POOLLINK_T *p;
89
90 p = sm_malloc(sizeof(SM_POOLHDR_T) + size);
91 if (p == NULL)
92 return NULL;
93 p->sm_pnext = rpool->sm_pools;
94 rpool->sm_pools = p;
95 return (char*) p + sizeof(SM_POOLHDR_T);
96 }
97
98 /*
99 ** SM_RPOOL_MALLOC_TAGGED_X -- allocate memory from rpool
100 **
101 ** Parameters:
102 ** rpool -- rpool from which memory should be allocated;
103 ** can be NULL, use sm_malloc() then.
104 ** size -- size of block.
105 ** file -- filename.
106 ** line -- line number in file.
107 ** group -- heap group for debugging.
108 **
109 ** Returns:
110 ** Pointer to block.
111 **
112 ** Exceptions:
113 ** F:sm_heap -- out of memory
114 **
115 ** Notice: XXX
116 ** if size == 0 and the rpool is new (no memory
117 ** allocated yet) NULL is returned!
118 ** We could solve this by
119 ** - wasting 1 byte (size < avail)
120 ** - checking for rpool->sm_poolptr != NULL
121 ** - not asking for 0 sized buffer
122 */
123
124 void *
125 #if SM_HEAP_CHECK
sm_rpool_malloc_tagged_x(rpool,size,file,line,group)126 sm_rpool_malloc_tagged_x(rpool, size, file, line, group)
127 SM_RPOOL_T *rpool;
128 size_t size;
129 char *file;
130 int line;
131 int group;
132 #else /* SM_HEAP_CHECK */
133 sm_rpool_malloc_x(rpool, size)
134 SM_RPOOL_T *rpool;
135 size_t size;
136 #endif /* SM_HEAP_CHECK */
137 {
138 char *ptr;
139
140 if (rpool == NULL)
141 return sm_malloc_tagged_x(size, file, line, group);
142
143 /* Ensure that size is properly aligned. */
144 if (size & SM_ALIGN_BITS)
145 size = (size & ~SM_ALIGN_BITS) + SM_ALIGN_SIZE;
146
147 /* The common case. This is optimized for speed. */
148 if (size <= rpool->sm_poolavail)
149 {
150 ptr = rpool->sm_poolptr;
151 rpool->sm_poolptr += size;
152 rpool->sm_poolavail -= size;
153 return ptr;
154 }
155
156 /*
157 ** The slow case: we need to call malloc.
158 ** The SM_REQUIRE assertion is deferred until now, for speed.
159 ** That's okay: we set rpool->sm_poolavail to 0 when we free an rpool,
160 ** so the common case code won't be triggered on a dangling pointer.
161 */
162
163 SM_REQUIRE(rpool->sm_magic == SmRpoolMagic);
164
165 /*
166 ** If size > sm_poolsize, then malloc a new block especially for
167 ** this request. Future requests will be allocated from the
168 ** current pool.
169 **
170 ** What if the current pool is mostly unallocated, and the current
171 ** request is larger than the available space, but < sm_poolsize?
172 ** If we discard the current pool, and start allocating from a new
173 ** pool, then we will be wasting a lot of space. For this reason,
174 ** we malloc a block just for the current request if size >
175 ** sm_bigobjectsize, where sm_bigobjectsize <= sm_poolsize.
176 ** Thus, the most space that we will waste at the end of a pool
177 ** is sm_bigobjectsize - 1.
178 */
179
180 if (size > rpool->sm_bigobjectsize)
181 {
182 #if _FFR_PERF_RPOOL
183 ++rpool->sm_nbigblocks;
184 #endif
185 return sm_rpool_allocblock_x(rpool, size);
186 }
187 SM_ASSERT(rpool->sm_bigobjectsize <= rpool->sm_poolsize);
188 ptr = sm_rpool_allocblock_x(rpool, rpool->sm_poolsize);
189 rpool->sm_poolptr = ptr + size;
190 rpool->sm_poolavail = rpool->sm_poolsize - size;
191 #if _FFR_PERF_RPOOL
192 ++rpool->sm_npools;
193 #endif
194 return ptr;
195 }
196
197 /*
198 ** SM_RPOOL_MALLOC_TAGGED -- allocate memory from rpool
199 **
200 ** Parameters:
201 ** rpool -- rpool from which memory should be allocated;
202 ** can be NULL, use sm_malloc() then.
203 ** size -- size of block.
204 ** file -- filename.
205 ** line -- line number in file.
206 ** group -- heap group for debugging.
207 **
208 ** Returns:
209 ** Pointer to block, NULL on failure.
210 **
211 ** Notice: XXX
212 ** if size == 0 and the rpool is new (no memory
213 ** allocated yet) NULL is returned!
214 ** We could solve this by
215 ** - wasting 1 byte (size < avail)
216 ** - checking for rpool->sm_poolptr != NULL
217 ** - not asking for 0 sized buffer
218 */
219
220 void *
221 #if SM_HEAP_CHECK
sm_rpool_malloc_tagged(rpool,size,file,line,group)222 sm_rpool_malloc_tagged(rpool, size, file, line, group)
223 SM_RPOOL_T *rpool;
224 size_t size;
225 char *file;
226 int line;
227 int group;
228 #else /* SM_HEAP_CHECK */
229 sm_rpool_malloc(rpool, size)
230 SM_RPOOL_T *rpool;
231 size_t size;
232 #endif /* SM_HEAP_CHECK */
233 {
234 char *ptr;
235
236 if (rpool == NULL)
237 return sm_malloc_tagged(size, file, line, group);
238
239 /* Ensure that size is properly aligned. */
240 if (size & SM_ALIGN_BITS)
241 size = (size & ~SM_ALIGN_BITS) + SM_ALIGN_SIZE;
242
243 /* The common case. This is optimized for speed. */
244 if (size <= rpool->sm_poolavail)
245 {
246 ptr = rpool->sm_poolptr;
247 rpool->sm_poolptr += size;
248 rpool->sm_poolavail -= size;
249 return ptr;
250 }
251
252 /*
253 ** The slow case: we need to call malloc.
254 ** The SM_REQUIRE assertion is deferred until now, for speed.
255 ** That's okay: we set rpool->sm_poolavail to 0 when we free an rpool,
256 ** so the common case code won't be triggered on a dangling pointer.
257 */
258
259 SM_REQUIRE(rpool->sm_magic == SmRpoolMagic);
260
261 /*
262 ** If size > sm_poolsize, then malloc a new block especially for
263 ** this request. Future requests will be allocated from the
264 ** current pool.
265 **
266 ** What if the current pool is mostly unallocated, and the current
267 ** request is larger than the available space, but < sm_poolsize?
268 ** If we discard the current pool, and start allocating from a new
269 ** pool, then we will be wasting a lot of space. For this reason,
270 ** we malloc a block just for the current request if size >
271 ** sm_bigobjectsize, where sm_bigobjectsize <= sm_poolsize.
272 ** Thus, the most space that we will waste at the end of a pool
273 ** is sm_bigobjectsize - 1.
274 */
275
276 if (size > rpool->sm_bigobjectsize)
277 {
278 #if _FFR_PERF_RPOOL
279 ++rpool->sm_nbigblocks;
280 #endif
281 return sm_rpool_allocblock(rpool, size);
282 }
283 SM_ASSERT(rpool->sm_bigobjectsize <= rpool->sm_poolsize);
284 ptr = sm_rpool_allocblock(rpool, rpool->sm_poolsize);
285 if (ptr == NULL)
286 return NULL;
287 rpool->sm_poolptr = ptr + size;
288 rpool->sm_poolavail = rpool->sm_poolsize - size;
289 #if _FFR_PERF_RPOOL
290 ++rpool->sm_npools;
291 #endif
292 return ptr;
293 }
294
295 /*
296 ** SM_RPOOL_NEW_X -- create a new rpool.
297 **
298 ** Parameters:
299 ** parent -- pointer to parent rpool, can be NULL.
300 **
301 ** Returns:
302 ** Pointer to new rpool.
303 */
304
305 SM_RPOOL_T *
sm_rpool_new_x(parent)306 sm_rpool_new_x(parent)
307 SM_RPOOL_T *parent;
308 {
309 SM_RPOOL_T *rpool;
310
311 rpool = sm_malloc_x(sizeof(SM_RPOOL_T));
312 if (parent == NULL)
313 rpool->sm_parentlink = NULL;
314 else
315 {
316 SM_TRY
317 rpool->sm_parentlink = sm_rpool_attach_x(parent,
318 (SM_RPOOL_RFREE_T) sm_rpool_free,
319 (void *) rpool);
320 SM_EXCEPT(exc, "*")
321 sm_free(rpool);
322 sm_exc_raise_x(exc);
323 SM_END_TRY
324 }
325 rpool->sm_magic = SmRpoolMagic;
326
327 rpool->sm_poolsize = POOLSIZE - sizeof(SM_POOLHDR_T);
328 rpool->sm_bigobjectsize = rpool->sm_poolsize / BIG_OBJECT_RATIO;
329 rpool->sm_poolptr = NULL;
330 rpool->sm_poolavail = 0;
331 rpool->sm_pools = NULL;
332
333 rpool->sm_rptr = NULL;
334 rpool->sm_ravail = 0;
335 rpool->sm_rlists = NULL;
336 #if _FFR_PERF_RPOOL
337 rpool->sm_nbigblocks = 0;
338 rpool->sm_npools = 0;
339 #endif
340
341 return rpool;
342 }
343
344 /*
345 ** SM_RPOOL_SETSIZES -- set sizes for rpool.
346 **
347 ** Parameters:
348 ** poolsize -- size of a single rpool block.
349 ** bigobjectsize -- if this size is exceeded, an individual
350 ** block is allocated (must be less or equal poolsize).
351 **
352 ** Returns:
353 ** none.
354 */
355
356 void
sm_rpool_setsizes(rpool,poolsize,bigobjectsize)357 sm_rpool_setsizes(rpool, poolsize, bigobjectsize)
358 SM_RPOOL_T *rpool;
359 size_t poolsize;
360 size_t bigobjectsize;
361 {
362 SM_REQUIRE(poolsize >= bigobjectsize);
363 if (poolsize == 0)
364 poolsize = POOLSIZE - sizeof(SM_POOLHDR_T);
365 if (bigobjectsize == 0)
366 bigobjectsize = poolsize / BIG_OBJECT_RATIO;
367 rpool->sm_poolsize = poolsize;
368 rpool->sm_bigobjectsize = bigobjectsize;
369 }
370
371 /*
372 ** SM_RPOOL_FREE -- free an rpool and release all of its resources.
373 **
374 ** Parameters:
375 ** rpool -- rpool to free.
376 **
377 ** Returns:
378 ** none.
379 */
380
381 void
sm_rpool_free(rpool)382 sm_rpool_free(rpool)
383 SM_RPOOL_T *rpool;
384 {
385 SM_RLIST_T *rl, *rnext;
386 SM_RESOURCE_T *r, *rmax;
387 SM_POOLLINK_T *pp, *pnext;
388
389 if (rpool == NULL)
390 return;
391
392 /*
393 ** It's important to free the resources before the memory pools,
394 ** because the resource free functions might modify the contents
395 ** of the memory pools.
396 */
397
398 rl = rpool->sm_rlists;
399 if (rl != NULL)
400 {
401 rmax = rpool->sm_rptr;
402 for (;;)
403 {
404 for (r = rl->sm_rvec; r < rmax; ++r)
405 {
406 if (r->sm_rfree != NULL)
407 r->sm_rfree(r->sm_rcontext);
408 }
409 rnext = rl->sm_rnext;
410 sm_free(rl);
411 if (rnext == NULL)
412 break;
413 rl = rnext;
414 rmax = &rl->sm_rvec[SM_RLIST_MAX];
415 }
416 }
417
418 /*
419 ** Now free the memory pools.
420 */
421
422 for (pp = rpool->sm_pools; pp != NULL; pp = pnext)
423 {
424 pnext = pp->sm_pnext;
425 sm_free(pp);
426 }
427
428 /*
429 ** Disconnect rpool from its parent.
430 */
431
432 if (rpool->sm_parentlink != NULL)
433 *rpool->sm_parentlink = NULL;
434
435 /*
436 ** Setting these fields to zero means that any future to attempt
437 ** to use the rpool after it is freed will cause an assertion failure.
438 */
439
440 rpool->sm_magic = NULL;
441 rpool->sm_poolavail = 0;
442 rpool->sm_ravail = 0;
443
444 #if _FFR_PERF_RPOOL
445 if (rpool->sm_nbigblocks > 0 || rpool->sm_npools > 1)
446 syslog(LOG_NOTICE,
447 "perf: rpool=%lx, sm_nbigblocks=%d, sm_npools=%d",
448 (long) rpool, rpool->sm_nbigblocks, rpool->sm_npools);
449 rpool->sm_nbigblocks = 0;
450 rpool->sm_npools = 0;
451 #endif /* _FFR_PERF_RPOOL */
452 sm_free(rpool);
453 }
454
455 /*
456 ** SM_RPOOL_ATTACH_X -- attach a resource to an rpool.
457 **
458 ** Parameters:
459 ** rpool -- rpool to which resource should be attached.
460 ** rfree -- function to call when rpool is freed.
461 ** rcontext -- argument for function to call when rpool is freed.
462 **
463 ** Returns:
464 ** Pointer to allocated function.
465 **
466 ** Exceptions:
467 ** F:sm_heap -- out of memory
468 */
469
470 SM_RPOOL_ATTACH_T
sm_rpool_attach_x(rpool,rfree,rcontext)471 sm_rpool_attach_x(rpool, rfree, rcontext)
472 SM_RPOOL_T *rpool;
473 SM_RPOOL_RFREE_T rfree;
474 void *rcontext;
475 {
476 SM_RLIST_T *rl;
477 SM_RPOOL_ATTACH_T a;
478
479 SM_REQUIRE_ISA(rpool, SmRpoolMagic);
480
481 if (rpool->sm_ravail == 0)
482 {
483 rl = sm_malloc_x(sizeof(SM_RLIST_T));
484 rl->sm_rnext = rpool->sm_rlists;
485 rpool->sm_rlists = rl;
486 rpool->sm_rptr = rl->sm_rvec;
487 rpool->sm_ravail = SM_RLIST_MAX;
488 }
489
490 a = &rpool->sm_rptr->sm_rfree;
491 rpool->sm_rptr->sm_rfree = rfree;
492 rpool->sm_rptr->sm_rcontext = rcontext;
493 ++rpool->sm_rptr;
494 --rpool->sm_ravail;
495 return a;
496 }
497
498 #if DO_NOT_USE_STRCPY
499 /*
500 ** SM_RPOOL_STRDUP_X -- Create a copy of a C string
501 **
502 ** Parameters:
503 ** rpool -- rpool to use.
504 ** s -- the string to copy.
505 **
506 ** Returns:
507 ** pointer to newly allocated string.
508 */
509
510 char *
511 # if SM_HEAP_CHECK > 2
sm_rpool_strdup_tagged_x(rpool,s,tag,line,group)512 sm_rpool_strdup_tagged_x
513 # else
514 sm_rpool_strdup_x
515 # endif
516 (rpool, s
517 # if SM_HEAP_CHECK > 2
518 , tag, line, group
519 # endif
520 )
521 SM_RPOOL_T *rpool;
522 const char *s;
523 # if SM_HEAP_CHECK > 2
524 char *tag;
525 int line;
526 int group;
527 # else
528 # define tag "sm_rpool_strdup_x"
529 # define line 1
530 # define group 1
531 # endif
532 {
533 size_t l;
534 char *n;
535
536 l = strlen(s);
537 SM_ASSERT(l + 1 > l);
538 # if SM_HEAP_CHECK
539 n = sm_rpool_malloc_tagged_x(rpool, l + 1, tag, line, group);
540 # else
541 n = sm_rpool_malloc_x(rpool, l + 1);
542 # endif
543 sm_strlcpy(n, s, l + 1);
544 return n;
545 }
546 # if SM_HEAP_CHECK <= 2
547 # undef tag
548 # undef line
549 # undef group
550 # endif
551 #endif /* DO_NOT_USE_STRCPY */
552