1320731Szbb/*
2368013Smw * Copyright (c) 2000-2004 Proofpoint, Inc. and its suppliers.
3320731Szbb *	All rights reserved.
4361534Smw *
5320731Szbb * By using this file, you agree to the terms and conditions set
6320731Szbb * forth in the LICENSE file which can be found at the top level of
7320731Szbb * the sendmail distribution.
8320731Szbb */
9320731Szbb
10320731Szbb#include <sm/gen.h>
11320731SzbbSM_RCSID("@(#)$Id: rpool.c,v 1.29 2013-11-22 20:51:43 ca Exp $")
12320731Szbb
13320731Szbb/*
14320731Szbb**  resource pools
15320731Szbb**  For documentation, see rpool.html
16320731Szbb*/
17320731Szbb
18320731Szbb#include <sm/exc.h>
19320731Szbb#include <sm/heap.h>
20320731Szbb#include <sm/rpool.h>
21320731Szbb#include <sm/varargs.h>
22320731Szbb#include <sm/conf.h>
23320731Szbb#if _FFR_PERF_RPOOL
24320731Szbb# include <syslog.h>
25320731Szbb#endif
26320731Szbb
27320731Szbbconst char SmRpoolMagic[] = "sm_rpool";
28320731Szbb
29320731Szbbtypedef union
30320731Szbb{
31320731Szbb	SM_POOLLINK_T	link;
32320731Szbb	char		align[SM_ALIGN_SIZE];
33320731Szbb} SM_POOLHDR_T;
34320731Szbb
35320731Szbbstatic char	*sm_rpool_allocblock_x __P((SM_RPOOL_T *, size_t));
36320731Szbbstatic char	*sm_rpool_allocblock __P((SM_RPOOL_T *, size_t));
37320731Szbb
38320731Szbb/*
39320731Szbb**  Tune this later
40320731Szbb*/
41320731Szbb
42320731Szbb#define POOLSIZE		4096
43320731Szbb#define BIG_OBJECT_RATIO	10
44320731Szbb
45320731Szbb/*
46320731Szbb**  SM_RPOOL_ALLOCBLOCK_X -- allocate a new block for an rpool.
47320731Szbb**
48320731Szbb**	Parameters:
49320731Szbb**		rpool -- rpool to which the block should be added.
50320731Szbb**		size -- size of block.
51320731Szbb**
52320731Szbb**	Returns:
53320731Szbb**		Pointer to block.
54320731Szbb**
55320731Szbb**	Exceptions:
56320731Szbb**		F:sm_heap -- out of memory
57320731Szbb*/
58320731Szbb
59320731Szbbstatic char *
60320731Szbbsm_rpool_allocblock_x(rpool, size)
61320731Szbb	SM_RPOOL_T *rpool;
62320731Szbb	size_t size;
63320731Szbb{
64320731Szbb	SM_POOLLINK_T *p;
65320731Szbb
66320731Szbb	p = sm_malloc_x(sizeof(SM_POOLHDR_T) + size);
67320731Szbb	p->sm_pnext = rpool->sm_pools;
68320731Szbb	rpool->sm_pools = p;
69320731Szbb	return (char*) p + sizeof(SM_POOLHDR_T);
70320731Szbb}
71320731Szbb
72320731Szbb/*
73320731Szbb**  SM_RPOOL_ALLOCBLOCK -- allocate a new block for an rpool.
74361467Smw**
75320731Szbb**	Parameters:
76320731Szbb**		rpool -- rpool to which the block should be added.
77320731Szbb**		size -- size of block.
78320731Szbb**
79320731Szbb**	Returns:
80361534Smw**		Pointer to block, NULL on failure.
81320731Szbb*/
82320731Szbb
83320731Szbbstatic char *
84320731Szbbsm_rpool_allocblock(rpool, size)
85320731Szbb	SM_RPOOL_T *rpool;
86320731Szbb	size_t size;
87320731Szbb{
88320731Szbb	SM_POOLLINK_T *p;
89320731Szbb
90320731Szbb	p = sm_malloc(sizeof(SM_POOLHDR_T) + size);
91320731Szbb	if (p == NULL)
92320731Szbb		return NULL;
93320731Szbb	p->sm_pnext = rpool->sm_pools;
94320731Szbb	rpool->sm_pools = p;
95361467Smw	return (char*) p + sizeof(SM_POOLHDR_T);
96320731Szbb}
97320731Szbb
98320731Szbb/*
99320731Szbb**  SM_RPOOL_MALLOC_TAGGED_X -- allocate memory from rpool
100320731Szbb**
101320731Szbb**	Parameters:
102320731Szbb**		rpool -- rpool from which memory should be allocated;
103361534Smw**			can be NULL, use sm_malloc() then.
104320731Szbb**		size -- size of block.
105320731Szbb**		file -- filename.
106320731Szbb**		line -- line number in file.
107320731Szbb**		group -- heap group for debugging.
108320731Szbb**
109320731Szbb**	Returns:
110320731Szbb**		Pointer to block.
111320731Szbb**
112320731Szbb**	Exceptions:
113320731Szbb**		F:sm_heap -- out of memory
114343397Smw**
115343397Smw**	Notice: XXX
116343397Smw**		if size == 0 and the rpool is new (no memory
117343397Smw**		allocated yet) NULL is returned!
118343397Smw**		We could solve this by
119343397Smw**		- wasting 1 byte (size < avail)
120343397Smw**		- checking for rpool->sm_poolptr != NULL
121361534Smw**		- not asking for 0 sized buffer
122343397Smw*/
123343397Smw
124343397Smwvoid *
125343397Smw#if SM_HEAP_CHECK
126343397Smwsm_rpool_malloc_tagged_x(rpool, size, file, line, group)
127343397Smw	SM_RPOOL_T *rpool;
128343397Smw	size_t size;
129343397Smw	char *file;
130361534Smw	int line;
131343397Smw	int group;
132343397Smw#else /* SM_HEAP_CHECK */
133361467Smwsm_rpool_malloc_x(rpool, size)
134361467Smw	SM_RPOOL_T *rpool;
135361467Smw	size_t size;
136361467Smw#endif /* SM_HEAP_CHECK */
137361467Smw{
138361467Smw	char *ptr;
139361467Smw
140361467Smw	if (rpool == NULL)
141361467Smw		return sm_malloc_tagged_x(size, file, line, group);
142361467Smw
143361467Smw	/* Ensure that size is properly aligned. */
144361467Smw	if (size & SM_ALIGN_BITS)
145361467Smw		size = (size & ~SM_ALIGN_BITS) + SM_ALIGN_SIZE;
146361467Smw
147361467Smw	/* The common case.  This is optimized for speed. */
148361467Smw	if (size <= rpool->sm_poolavail)
149361467Smw	{
150361467Smw		ptr = rpool->sm_poolptr;
151361467Smw		rpool->sm_poolptr += size;
152361467Smw		rpool->sm_poolavail -= size;
153361467Smw		return ptr;
154361467Smw	}
155361467Smw
156361467Smw	/*
157361467Smw	**  The slow case: we need to call malloc.
158361467Smw	**  The SM_REQUIRE assertion is deferred until now, for speed.
159361467Smw	**  That's okay: we set rpool->sm_poolavail to 0 when we free an rpool,
160361467Smw	**  so the common case code won't be triggered on a dangling pointer.
161361467Smw	*/
162361467Smw
163361467Smw	SM_REQUIRE(rpool->sm_magic == SmRpoolMagic);
164361534Smw
165361534Smw	/*
166361467Smw	**  If size > sm_poolsize, then malloc a new block especially for
167361467Smw	**  this request.  Future requests will be allocated from the
168361467Smw	**  current pool.
169361467Smw	**
170361467Smw	**  What if the current pool is mostly unallocated, and the current
171361467Smw	**  request is larger than the available space, but < sm_poolsize?
172361467Smw	**  If we discard the current pool, and start allocating from a new
173361467Smw	**  pool, then we will be wasting a lot of space.  For this reason,
174368013Smw	**  we malloc a block just for the current request if size >
175368013Smw	**  sm_bigobjectsize, where sm_bigobjectsize <= sm_poolsize.
176361467Smw	**  Thus, the most space that we will waste at the end of a pool
177361467Smw	**  is sm_bigobjectsize - 1.
178361467Smw	*/
179361467Smw
180361467Smw	if (size > rpool->sm_bigobjectsize)
181320731Szbb	{
182320731Szbb#if _FFR_PERF_RPOOL
183361534Smw		++rpool->sm_nbigblocks;
184361467Smw#endif
185320731Szbb		return sm_rpool_allocblock_x(rpool, size);
186368013Smw	}
187368013Smw	SM_ASSERT(rpool->sm_bigobjectsize <= rpool->sm_poolsize);
188320731Szbb	ptr = sm_rpool_allocblock_x(rpool, rpool->sm_poolsize);
189320731Szbb	rpool->sm_poolptr = ptr + size;
190320731Szbb	rpool->sm_poolavail = rpool->sm_poolsize - size;
191320731Szbb#if _FFR_PERF_RPOOL
192361467Smw	++rpool->sm_npools;
193368013Smw#endif
194368013Smw	return ptr;
195368013Smw}
196361467Smw
197361467Smw/*
198361467Smw**  SM_RPOOL_MALLOC_TAGGED -- allocate memory from rpool
199320731Szbb**
200320731Szbb**	Parameters:
201320731Szbb**		rpool -- rpool from which memory should be allocated;
202320731Szbb**			can be NULL, use sm_malloc() then.
203320731Szbb**		size -- size of block.
204320731Szbb**		file -- filename.
205320731Szbb**		line -- line number in file.
206320731Szbb**		group -- heap group for debugging.
207361534Smw**
208361534Smw**	Returns:
209361534Smw**		Pointer to block, NULL on failure.
210361534Smw**
211320731Szbb**	Notice: XXX
212361534Smw**		if size == 0 and the rpool is new (no memory
213368013Smw**		allocated yet) NULL is returned!
214368013Smw**		We could solve this by
215361534Smw**		- wasting 1 byte (size < avail)
216361534Smw**		- checking for rpool->sm_poolptr != NULL
217361534Smw**		- not asking for 0 sized buffer
218361534Smw*/
219320731Szbb
220320731Szbbvoid *
221320731Szbb#if SM_HEAP_CHECK
222320731Szbbsm_rpool_malloc_tagged(rpool, size, file, line, group)
223320731Szbb	SM_RPOOL_T *rpool;
224320731Szbb	size_t size;
225320731Szbb	char *file;
226320731Szbb	int line;
227320731Szbb	int group;
228320731Szbb#else /* SM_HEAP_CHECK */
229320731Szbbsm_rpool_malloc(rpool, size)
230320731Szbb	SM_RPOOL_T *rpool;
231320731Szbb	size_t size;
232320731Szbb#endif /* SM_HEAP_CHECK */
233320731Szbb{
234320731Szbb	char *ptr;
235320731Szbb
236320731Szbb	if (rpool == NULL)
237320731Szbb		return sm_malloc_tagged(size, file, line, group);
238320731Szbb
239320731Szbb	/* Ensure that size is properly aligned. */
240320731Szbb	if (size & SM_ALIGN_BITS)
241320731Szbb		size = (size & ~SM_ALIGN_BITS) + SM_ALIGN_SIZE;
242320731Szbb
243361467Smw	/* The common case.  This is optimized for speed. */
244361467Smw	if (size <= rpool->sm_poolavail)
245361467Smw	{
246361467Smw		ptr = rpool->sm_poolptr;
247361467Smw		rpool->sm_poolptr += size;
248361467Smw		rpool->sm_poolavail -= size;
249361467Smw		return ptr;
250361467Smw	}
251361467Smw
252361467Smw	/*
253361467Smw	**  The slow case: we need to call malloc.
254361467Smw	**  The SM_REQUIRE assertion is deferred until now, for speed.
255361467Smw	**  That's okay: we set rpool->sm_poolavail to 0 when we free an rpool,
256361467Smw	**  so the common case code won't be triggered on a dangling pointer.
257361467Smw	*/
258361467Smw
259361467Smw	SM_REQUIRE(rpool->sm_magic == SmRpoolMagic);
260361467Smw
261361467Smw	/*
262361467Smw	**  If size > sm_poolsize, then malloc a new block especially for
263361467Smw	**  this request.  Future requests will be allocated from the
264361467Smw	**  current pool.
265361467Smw	**
266361467Smw	**  What if the current pool is mostly unallocated, and the current
267361467Smw	**  request is larger than the available space, but < sm_poolsize?
268361467Smw	**  If we discard the current pool, and start allocating from a new
269361467Smw	**  pool, then we will be wasting a lot of space.  For this reason,
270361467Smw	**  we malloc a block just for the current request if size >
271361467Smw	**  sm_bigobjectsize, where sm_bigobjectsize <= sm_poolsize.
272361467Smw	**  Thus, the most space that we will waste at the end of a pool
273361467Smw	**  is sm_bigobjectsize - 1.
274361467Smw	*/
275361467Smw
276361467Smw	if (size > rpool->sm_bigobjectsize)
277361467Smw	{
278368013Smw#if _FFR_PERF_RPOOL
279368013Smw		++rpool->sm_nbigblocks;
280361467Smw#endif
281361467Smw		return sm_rpool_allocblock(rpool, size);
282361467Smw	}
283361467Smw	SM_ASSERT(rpool->sm_bigobjectsize <= rpool->sm_poolsize);
284361467Smw	ptr = sm_rpool_allocblock(rpool, rpool->sm_poolsize);
285361467Smw	if (ptr == NULL)
286361467Smw		return NULL;
287361467Smw	rpool->sm_poolptr = ptr + size;
288320731Szbb	rpool->sm_poolavail = rpool->sm_poolsize - size;
289320731Szbb#if _FFR_PERF_RPOOL
290320731Szbb	++rpool->sm_npools;
291320731Szbb#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
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
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