1 /*
2  * Copyright (c) 2000-2004 Sendmail, 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.28 2004/08/03 20:44:04 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 /* _FFR_PERF_RPOOL */
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 /* _FFR_PERF_RPOOL */
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 /* _FFR_PERF_RPOOL */
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 /* _FFR_PERF_RPOOL */
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 /* _FFR_PERF_RPOOL */
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 /* _FFR_PERF_RPOOL */
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 *
sm_rpool_strdup_x(rpool,s)511 sm_rpool_strdup_x(rpool, s)
512 	SM_RPOOL_T *rpool;
513 	const char *s;
514 {
515 	size_t l;
516 	char *n;
517 
518 	l = strlen(s);
519 	SM_ASSERT(l + 1 > l);
520 	n = sm_rpool_malloc_x(rpool, l + 1);
521 	sm_strlcpy(n, s, l + 1);
522 	return n;
523 }
524 #endif /* DO_NOT_USE_STRCPY */
525