rpool.c revision 261363
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>
11SM_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 /* _FFR_PERF_RPOOL */
26
27const char SmRpoolMagic[] = "sm_rpool";
28
29typedef union
30{
31	SM_POOLLINK_T	link;
32	char		align[SM_ALIGN_SIZE];
33} SM_POOLHDR_T;
34
35static char	*sm_rpool_allocblock_x __P((SM_RPOOL_T *, size_t));
36static 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
59static char *
60sm_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
83static char *
84sm_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
124void *
125#if SM_HEAP_CHECK
126sm_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 */
133sm_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
220void *
221#if SM_HEAP_CHECK
222sm_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 */
229sm_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
305SM_RPOOL_T *
306sm_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
356void
357sm_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
381void
382sm_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
470SM_RPOOL_ATTACH_T
471sm_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
510char *
511sm_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