Annotation of libaitio/src/mem.c, revision 1.1.4.4
1.1.4.2 misho 1: /*************************************************************************
2: * (C) 2012 AITNET ltd - Sofia/Bulgaria - <misho@elwix.org>
3: * by Michael Pounov <misho@openbsd-bg.org>
4: *
5: * $Author: misho $
1.1.4.4 ! misho 6: * $Id: mem.c,v 1.1.4.3 2012/05/23 11:49:35 misho Exp $
1.1.4.2 misho 7: *
8: **************************************************************************
9: The ELWIX and AITNET software is distributed under the following
10: terms:
11:
12: All of the documentation and software included in the ELWIX and AITNET
13: Releases is copyrighted by ELWIX - Sofia/Bulgaria <info@elwix.org>
14:
15: Copyright 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
16: by Michael Pounov <misho@elwix.org>. All rights reserved.
17:
18: Redistribution and use in source and binary forms, with or without
19: modification, are permitted provided that the following conditions
20: are met:
21: 1. Redistributions of source code must retain the above copyright
22: notice, this list of conditions and the following disclaimer.
23: 2. Redistributions in binary form must reproduce the above copyright
24: notice, this list of conditions and the following disclaimer in the
25: documentation and/or other materials provided with the distribution.
26: 3. All advertising materials mentioning features or use of this software
27: must display the following acknowledgement:
28: This product includes software developed by Michael Pounov <misho@elwix.org>
29: ELWIX - Embedded LightWeight unIX and its contributors.
30: 4. Neither the name of AITNET nor the names of its contributors
31: may be used to endorse or promote products derived from this software
32: without specific prior written permission.
33:
34: THIS SOFTWARE IS PROVIDED BY AITNET AND CONTRIBUTORS ``AS IS'' AND
35: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
36: IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
37: ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
38: FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
39: DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
40: OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
41: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
42: LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
43: OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44: SUCH DAMAGE.
45: */
46: #include "global.h"
47:
48:
49: /*
50: * mpool_init() - Init memory pool
51: *
52: * @maxmem = If !=0 set maximum memory quota
53: * return: =NULL error or !=NULL new allocated pool
54: */
55: mpool_t *
56: mpool_init(u_long maxmem)
57: {
58: mpool_t *mp;
59: register int i;
60:
61: mp = malloc(sizeof(mpool_t));
62: if (!mp) {
63: LOGERR;
64: return NULL;
65: } else
66: memset(mp, 0, sizeof(mpool_t));
67:
68: pthread_mutex_init(&mp->pool_mtx, NULL);
69:
70: mp->pool_quota.max = maxmem;
71:
72: mpool_lock(mp);
73: for (i = 0; i < MEM_BUCKETS; i++) {
74: TAILQ_INIT(&mp->pool_active[i]);
75: TAILQ_INIT(&mp->pool_inactive[i]);
76: }
77: mpool_unlock(mp);
78:
79: return mp;
80: }
81:
82: /*
83: * mpool_destroy() - Destroy memory pool
84: *
85: * @mp = Memory pool
86: * return: none
87: */
88: void
89: mpool_destroy(mpool_t ** __restrict mp)
90: {
91: struct tagAlloc *m;
92: register int i;
93:
94: if (!mp && !*mp)
95: return;
96:
97: mpool_lock(*mp);
98:
99: for (i = 0; i < MEM_BUCKETS; i++) {
100: while ((m = TAILQ_FIRST(&(*mp)->pool_active[i]))) {
101: TAILQ_REMOVE(&(*mp)->pool_active[i], m, alloc_node);
102: if (m->alloc_mem)
103: free(m->alloc_mem);
104: free(m);
105: }
106: while ((m = TAILQ_FIRST(&(*mp)->pool_inactive[i]))) {
107: TAILQ_REMOVE(&(*mp)->pool_inactive[i], m, alloc_node);
108: if (m->alloc_mem)
109: free(m->alloc_mem);
110: free(m);
111: }
112: }
113:
114: mpool_unlock(*mp);
115: pthread_mutex_destroy(&(*mp)->pool_mtx);
116:
117: free(*mp);
118: *mp = NULL;
119: }
120:
121: /* ----------------------------------------------------------- */
122:
123: static inline long
124: BucketIndex(u_int size)
125: {
126: register long b;
127:
128: if (!size--)
129: return 0; /* min bucket position in array */
130:
131: for (b = MEM_MIN_BUCKET; b < MEM_MAX_BUCKET; b++)
132: if (!(size >> b))
133: break;
134:
135: return b - MEM_MIN_BUCKET; /* convert to bucket array index */
136: }
137:
138: static inline struct tagAlloc *
139: pullInactive(mpool_t * __restrict mp, int idx)
140: {
141: struct tagAlloc *m = NULL;
142:
143: /* must be locked pool before use this function */
144: if ((m = TAILQ_FIRST(&mp->pool_inactive[idx]))) {
145: TAILQ_REMOVE(&mp->pool_inactive[idx], m, alloc_node);
146: /* statistics */
147: mp->pool_calls.cache--;
148: mp->pool_bytes.cache -= mem_size(m);
149:
150: /* clear name */
151: *m->alloc_name = 0;
152: }
153:
154: return m;
155: }
156:
157: /*
158: * mpool_malloc() - Memory allocation
159: *
160: * @mp = Memory pool
161: * @size = Size
162: * @memname = Optional memory block name
163: * return: NULL error or !=NULL ok allocated memory
164: */
165: void *
166: mpool_malloc(mpool_t * __restrict mp, u_int size, const char *memname)
167: {
168: struct tagAlloc *m;
169: int idx;
170: u_int align;
171:
172: if (!mp) {
1.1.4.3 misho 173: io_SetErr(EINVAL, "Pool not specified");
1.1.4.2 misho 174: return NULL;
175: }
176: if (size > MEM_ALLOC_MAX) {
1.1.4.3 misho 177: io_SetErr(ENOMEM, "Memory size is too large");
1.1.4.2 misho 178: return NULL;
179: } else
180: size = (size + 3) & ~3; /* must align to 4 because needed room for sentinels */
181:
182: idx = BucketIndex(size);
183:
184: mpool_lock(mp);
185:
186: /* get memory from cache if exists */
187: if (!(m = pullInactive(mp, idx))) {
188: /* quota */
189: if (mp->pool_quota.max &&
190: (mp->pool_quota.curr + size) > mp->pool_quota.max) {
1.1.4.3 misho 191: io_SetErr(ENOMEM, "Max.allocate memory quota has been reached");
1.1.4.2 misho 192: mpool_unlock(mp);
193: return NULL;
194: }
195:
196: m = malloc(sizeof(struct tagAlloc));
197: if (!m) {
198: LOGERR;
199: mpool_unlock(mp);
200: return NULL;
201: } else
202: memset(m, 0, sizeof(struct tagAlloc));
203: }
204:
205: if (memname)
206: strlcpy(m->alloc_name, memname, sizeof m->alloc_name);
207:
208: if (!m->alloc_mem) {
209: align = 1 << (idx + MEM_MIN_BUCKET);
210: m->alloc_mem = malloc(align + 12); /* +12 sentinel bytes */
211: if (!m->alloc_mem) {
212: LOGERR;
213: free(m);
214: mpool_unlock(mp);
215: return NULL;
216: } else /* quota */
217: mp->pool_quota.curr += size;
218: }
219:
220: m->alloc_mem[0] = size / sizeof(u_int);
221: m->alloc_mem[1] = MEM_MAGIC_START;
222: m->alloc_mem[2 + size / sizeof(u_int)] = MEM_MAGIC_STOP;
223: TAILQ_INSERT_HEAD(&mp->pool_active[idx], m, alloc_node);
224: /* statistics */
225: mp->pool_calls.alloc++;
226: mp->pool_bytes.alloc += size;
227:
228: mpool_unlock(mp);
229: return mem_data(m, void*);
230: }
231:
232: /*
233: * mpool_realloc() Reallocate memory block with new size
234: *
235: * @mp = Memory pool
236: * @data = Allocated memory data
237: * @newsize = New size of memory block
238: * @memname = Optional new memory block name
239: * return: NULL error or !=NULL new reallocated memory block
240: */
241: void *
242: mpool_realloc(mpool_t * __restrict mp, void * __restrict data, u_int newsize, const char *memname)
243: {
244: struct tagAlloc *m, *tmp;
245: int idx, oidx;
246: void *p;
247: u_int align, osize;
248:
249: /* if !data execute mpool_malloc() */
250: if (!data)
251: return mpool_malloc(mp, newsize, memname);
252:
253: if (!mp) {
1.1.4.3 misho 254: io_SetErr(EINVAL, "Pool not specified");
1.1.4.2 misho 255: return NULL;
256: }
257: /* check address range & sentinel */
258: if (MEM_BADADDR(data) || MEM_CORRUPT(data)) {
1.1.4.3 misho 259: io_SetErr(EFAULT, "Corrupted memory address");
1.1.4.2 misho 260: return NULL;
261: } else {
262: osize = ((u_int*)data)[-2] * sizeof(u_int);
263: oidx = BucketIndex(osize);
264: }
265: /* prepare new size */
266: if (newsize > MEM_ALLOC_MAX) {
1.1.4.3 misho 267: io_SetErr(ENOMEM, "Memory size is too large");
1.1.4.2 misho 268: return NULL;
269: } else {
270: newsize = (newsize + 3) & ~3; /* must align to 4 because needed room for sentinels */
271: idx = BucketIndex(newsize);
272: }
273:
274: mpool_lock(mp);
275:
276: /* quota */
277: if (mp->pool_quota.max &&
278: (mp->pool_quota.curr + ((u_long) newsize - osize)) > mp->pool_quota.max) {
1.1.4.3 misho 279: io_SetErr(ENOMEM, "Max.allocate memory quota has been reached");
1.1.4.2 misho 280: mpool_unlock(mp);
281: return NULL;
282: }
283:
284: /* find old memory block */
285: TAILQ_FOREACH_SAFE(m, &mp->pool_active[oidx], alloc_node, tmp) {
286: if (mem_data(m, void*) == data && mem_size(m) == osize) {
287: /* case in different buckets */
288: if (oidx != idx) {
289: TAILQ_REMOVE(&mp->pool_active[oidx], m, alloc_node);
290: /* statistics */
291: mp->pool_calls.alloc--;
292: }
293: mp->pool_bytes.alloc -= osize;
294: break;
295: }
296: }
297: /* memory block not found! */
298: if (!m) {
299: mpool_unlock(mp);
1.1.4.3 misho 300: io_SetErr(EFAULT, "Memory block not found");
1.1.4.2 misho 301: return NULL;
302: }
303:
304: /* try to reallocate memory block to new bucket */
305: if (oidx != idx) {
306: align = 1 << (idx + MEM_MIN_BUCKET);
307: p = realloc(m->alloc_mem, align + 12);
308: if (!p) {
309: LOGERR;
310:
311: /* restore to old bucket pulled memory block for reallocation */
312: TAILQ_INSERT_HEAD(&mp->pool_active[oidx], m, alloc_node);
313: /* statistics */
314: mp->pool_calls.alloc++;
315: mp->pool_bytes.alloc += osize;
316:
317: mpool_unlock(mp);
318: return NULL;
319: } else
320: m->alloc_mem = (u_int*) p;
321: }
322: /* quota */
323: mp->pool_quota.curr += (u_long) newsize - osize;
324:
325: m->alloc_mem[0] = newsize / sizeof(u_int);
326: m->alloc_mem[1] = MEM_MAGIC_START;
327: m->alloc_mem[2 + newsize / sizeof(u_int)] = MEM_MAGIC_STOP;
328:
329: if (oidx != idx) {
330: TAILQ_INSERT_HEAD(&mp->pool_active[idx], m, alloc_node);
331: /* statistics */
332: mp->pool_calls.alloc++;
333: }
334: mp->pool_bytes.alloc += newsize;
335:
336: if (memname)
337: strlcpy(m->alloc_name, memname, sizeof m->alloc_name);
338:
339: mpool_unlock(mp);
340: return mem_data(m, void*);
341: }
342:
343: /*
344: * mpool_purge() - Purge memory block cache and release resources
345: *
346: * @mp = Memory pool
347: * @atmost = Free at most in buckets
348: * return: -1 error or 0 ok
349: */
350: int
351: mpool_purge(mpool_t * __restrict mp, u_int atmost)
352: {
353: register int i, cx;
354: struct tagAlloc *m, *tmp;
355:
356: if (!mp) {
1.1.4.3 misho 357: io_SetErr(EINVAL, "Pool not specified");
1.1.4.2 misho 358: return -1;
359: }
360:
361: mpool_lock(mp);
362:
363: for (i = cx = 0; i < MEM_BUCKETS; cx = 0, i++) {
364: TAILQ_FOREACH_SAFE(m, &mp->pool_inactive[i], alloc_node, tmp) {
365: /* barrier for purge */
366: if (cx < atmost) {
367: cx++;
368: continue;
369: }
370:
371: TAILQ_REMOVE(&mp->pool_inactive[i], m, alloc_node);
372: /* statistics */
373: mp->pool_calls.cache--;
374: mp->pool_bytes.cache -= mem_size(m);
375:
376: mp->pool_calls.free++;
377: mp->pool_bytes.free += mem_size(m);
378: /* quota */
379: mp->pool_quota.curr -= mem_size(m);
380:
381: if (m->alloc_mem)
382: free(m->alloc_mem);
383: free(m);
384: }
385: }
386:
387: mpool_unlock(mp);
388: return 0;
389: }
390:
391: /*
392: * mpool_free() Free allocated memory with mpool_alloc()
393: *
394: * @mp = Memory pool
395: * @data = Allocated memory data
396: * @purge = if !=0 force release memory block
397: * return: <0 error or 0 ok released memory block
398: */
399: int
400: mpool_free(mpool_t * __restrict mp, void * __restrict data, int purge)
401: {
402: int idx;
403: struct tagAlloc *m, *tmp;
404:
405: if (!mp) {
1.1.4.3 misho 406: io_SetErr(EINVAL, "Pool not specified");
1.1.4.2 misho 407: return -1;
408: }
409: /* check address range & sentinel */
410: if (MEM_BADADDR(data) || MEM_CORRUPT(data)) {
1.1.4.3 misho 411: io_SetErr(EFAULT, "Corrupted memory address");
1.1.4.2 misho 412: return -2;
413: } else
414: idx = BucketIndex(((u_int*)data)[-2] * sizeof(u_int));
415:
416: mpool_lock(mp);
417: TAILQ_FOREACH_SAFE(m, &mp->pool_active[idx], alloc_node, tmp)
418: if (mem_data(m, void*) == data) {
419: TAILQ_REMOVE(&mp->pool_active[idx], m, alloc_node);
420: /* statistics */
421: mp->pool_calls.alloc--;
422: mp->pool_bytes.alloc -= mem_size(m);
423:
424: if (!purge) {
425: TAILQ_INSERT_HEAD(&mp->pool_inactive[idx], m, alloc_node);
426: /* statistics */
427: mp->pool_calls.cache++;
428: mp->pool_bytes.cache += mem_size(m);
429: } else {
430: /* statistics */
431: mp->pool_calls.free++;
432: mp->pool_bytes.free += mem_size(m);
433: /* quota */
434: mp->pool_quota.curr -= mem_size(m);
435:
436: if (m->alloc_mem)
437: free(m->alloc_mem);
438: free(m);
439: }
440: break;
441: }
442: mpool_unlock(mp);
443:
444: return 0;
445: }
446:
447: /*
448: * mpool_free2() Free allocated memory with mpool_alloc() by size and memory name
449: *
450: * @mp = Memory pool
451: * @size = Allocated memory data size
452: * @memname = Memory name
453: * @purge = if !=0 force release memory block
454: * return: <0 error or 0 ok released memory block
455: */
456: int
457: mpool_free2(mpool_t * __restrict mp, u_int size, const char *memname, int purge)
458: {
459: int idx;
460: struct tagAlloc *m, *tmp;
461:
462: if (!mp || !memname) {
1.1.4.3 misho 463: io_SetErr(EINVAL, "Pool or memory name is not specified");
1.1.4.2 misho 464: return -1;
465: } else
466: idx = BucketIndex(size);
467:
468: mpool_lock(mp);
469: TAILQ_FOREACH_SAFE(m, &mp->pool_active[idx], alloc_node, tmp)
470: if (!strcmp(m->alloc_name, memname)) {
471: TAILQ_REMOVE(&mp->pool_active[idx], m, alloc_node);
472: /* statistics */
473: mp->pool_calls.alloc--;
474: mp->pool_bytes.alloc -= mem_size(m);
475:
476: if (!purge) {
477: TAILQ_INSERT_HEAD(&mp->pool_inactive[idx], m, alloc_node);
478: /* statistics */
479: mp->pool_calls.cache++;
480: mp->pool_bytes.cache += mem_size(m);
481: } else {
482: /* statistics */
483: mp->pool_calls.free++;
484: mp->pool_bytes.free += mem_size(m);
485: /* quota */
486: mp->pool_quota.curr -= mem_size(m);
487:
488: if (m->alloc_mem)
489: free(m->alloc_mem);
490: free(m);
491: }
492: break;
493: }
494: mpool_unlock(mp);
495:
496: return 0;
497: }
498:
499: /*
1.1.4.4 ! misho 500: * mpool_strdup() - String duplicate
! 501: *
! 502: * @mp = Memory pool
! 503: * @str = String
! 504: * @memname = Memory name
! 505: * return: NULL error or !=NULL new string
! 506: */
! 507: char *
! 508: mpool_strdup(mpool_t * __restrict mp, const char *str, const char *memname)
! 509: {
! 510: char *s = NULL;
! 511: u_int len;
! 512:
! 513: if (!mp) {
! 514: io_SetErr(EINVAL, "Pool not specified");
! 515: return NULL;
! 516: }
! 517: if (!str) {
! 518: io_SetErr(EINVAL, "String is NULL");
! 519: return NULL;
! 520: } else
! 521: len = strlen(str) + 1;
! 522:
! 523: s = mpool_malloc(mp, len, memname);
! 524: if (!s)
! 525: return NULL;
! 526: else
! 527: memcpy(s, str, len);
! 528:
! 529: return s;
! 530: }
! 531:
! 532: /*
1.1.4.2 misho 533: * mpool_getmembynam() Find allocated memory block by size and memory name
534: *
535: * @mp = Memory pool
536: * @size = Memory size
537: * @memname = Memory name
538: * return: NULL error or not found and !=NULL allocated memory
539: */
540: inline struct tagAlloc *
541: mpool_getmembynam(mpool_t * __restrict mp, u_int size, const char *memname)
542: {
543: int idx;
544: struct tagAlloc *m = NULL;
545:
546: if (!mp || !memname)
547: return NULL;
548:
549: idx = BucketIndex(size);
550: TAILQ_FOREACH(m, &mp->pool_active[idx], alloc_node)
551: if (!strcmp(m->alloc_name, memname))
552: break;
553:
554: return mem_data(m, void*);
555: }
556:
557: /*
558: * mpool_getsizebyaddr() - Get size of allocated memory block by address
559: *
560: * @addr = allocated memory from mpool_malloc()
561: * return: usable size of allocated memory block
562: */
563: inline u_int
564: mpool_getsizebyaddr(void * __restrict data)
565: {
566: if (mpool_chkaddr(data))
567: return 0;
568:
569: return (((u_int*) data)[-2] * sizeof(u_int));
570: }
571:
572: /*
573: * mpool_chkaddr() - Check validity of given address
574: *
575: * @data = allocated memory from mpool_malloc()
576: * return: -1 bad address, 1 corrupted address or 0 ok
577: */
578: inline int
579: mpool_chkaddr(void * __restrict data)
580: {
581: /* check address range */
582: if (MEM_BADADDR(data))
583: return -1;
584: /* check sentinel */
585: if (MEM_CORRUPT(data))
586: return 1;
587: /* data address is ok! */
588: return 0;
589: }
590:
591: /*
592: * mpool_setquota() - Change maximum memory quota
593: *
594: * @mp = Memory pool
595: * @maxmem = New max quota size
596: * return: old maximum memory quota size
597: */
598: inline u_long
599: mpool_setquota(mpool_t * __restrict mp, u_long maxmem)
600: {
601: u_long ret;
602:
603: if (!mp)
604: return 0;
605:
606: ret = mp->pool_quota.max;
607: mp->pool_quota.max = maxmem;
608:
609: /* if new max quota is less then current allocated memory,
610: * try to purge memory cache blocks
611: */
612: if (mp->pool_quota.max < mp->pool_quota.curr)
613: mpool_purge(mp, 0);
614:
615: return ret;
616: }
617:
618: /*
619: * mpool_getquota() - Get memory quota
620: *
621: * @mp = Memory pool
622: * @currmem = Return current memory
623: * @maxmem = Return max quota size
624: * return: none
625: */
626: inline void
627: mpool_getquota(mpool_t * __restrict mp, u_long *currmem, u_long *maxmem)
628: {
629: if (!mp)
630: return;
631:
632: if (maxmem)
633: *maxmem = mp->pool_quota.max;
634: if (currmem)
635: *currmem = mp->pool_quota.curr;
636: }
637:
638: /* ----------------------------------------------------------- */
639:
640: /*
641: * mpool_statistics() - Dump statistics from memory pool buckets
642: *
643: * @mp = Memory pool
644: * @cb = Export statistics to callback
645: * return: none
646: */
647: void
648: mpool_statistics(mpool_t * __restrict mp, mpool_stat_cb cb)
649: {
650: struct tagAlloc *m;
651: register int i, act, inact;
652:
653: if (!mp || !cb)
654: return;
655:
656: for (i = act = inact = 0; i < MEM_BUCKETS; act = inact = 0, i++) {
657: TAILQ_FOREACH(m, &mp->pool_active[i], alloc_node)
658: act++;
659: TAILQ_FOREACH(m, &mp->pool_inactive[i], alloc_node)
660: inact++;
661:
662: cb(1 << (i + MEM_MIN_BUCKET), act, inact);
663: }
664: }
1.1.4.4 ! misho 665:
! 666: /* ----------------------------------------------------------- */
! 667:
! 668: /*
! 669: * mpool_xmalloc() - malloc wrapper
! 670: *
! 671: * @size = Size
! 672: * return: NULL error or !=NULL ok allocated memory
! 673: */
! 674: void *
! 675: mpool_xmalloc(size_t size)
! 676: {
! 677: return mpool_malloc(io_mpool, size, NULL);
! 678: }
! 679:
! 680: /*
! 681: * mpool_xcalloc() - calloc wrapper
! 682: *
! 683: * @num = number of elements
! 684: * @size = Size of element
! 685: * return: NULL error or !=NULL ok allocated memory
! 686: */
! 687: void *
! 688: mpool_xcalloc(size_t num, size_t size)
! 689: {
! 690: return mpool_malloc(io_mpool, num * size, NULL);
! 691: }
! 692:
! 693: /*
! 694: * mpool_xrealloc() - realloc wrapper
! 695: *
! 696: * @data = Allocated memory data
! 697: * @newsize = New size of memory block
! 698: * return: NULL error or !=NULL new reallocated memory block
! 699: */
! 700: void *
! 701: mpool_xrealloc(void * __restrict data, size_t newsize)
! 702: {
! 703: return mpool_realloc(io_mpool, data, newsize, NULL);
! 704: }
! 705:
! 706: /*
! 707: * mpool_xfree() - free wrapper
! 708: *
! 709: * @data = Allocated memory data
! 710: * return: none
! 711: */
! 712: void
! 713: mpool_xfree(void * __restrict data)
! 714: {
! 715: mpool_free(io_mpool, data, 0);
! 716: }
! 717:
! 718: /*
! 719: * mpool_xstrdup() - strdup wrapper
! 720: *
! 721: * @str = string
! 722: * return: =NULL error or !=NULL new allocated string
! 723: */
! 724: char *
! 725: mpool_xstrdup(const char *str)
! 726: {
! 727: return mpool_strdup(io_mpool, str, NULL);
! 728: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>