xref: /illumos-gate/usr/src/lib/libumem/common/envvar.c (revision 5422785d352a2bb398daceab3d1898a8aa64d006)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * Copyright (c) 2012 Joyent, Inc. All rights reserved.
29  */
30 
31 #include <ctype.h>
32 #include <errno.h>
33 #include <limits.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <dlfcn.h>
37 #include "umem_base.h"
38 #include "vmem_base.h"
39 
40 /*
41  * A umem environment variable, like UMEM_DEBUG, is set to a series
42  * of items, seperated by ',':
43  *
44  *   UMEM_DEBUG="audit=10,guards,firewall=512"
45  *
46  * This structure describes items.  Each item has a name, type, and
47  * description.  During processing, an item read from the user may
48  * be either "valid" or "invalid".
49  *
50  * A valid item has an argument, if required, and it is of the right
51  * form (doesn't overflow, doesn't contain any unexpected characters).
52  *
53  * If the item is valid, item_flag_target != NULL, and:
54  *	type is not CLEARFLAG, then (*item_flag_target) |= item_flag_value
55  *	type is CLEARFLAG, then (*item_flag_target) &= ~item_flag_value
56  */
57 
58 #define	UMEM_ENV_ITEM_MAX	512
59 
60 struct umem_env_item;
61 
62 typedef int arg_process_t(const struct umem_env_item *item, const char *value);
63 #define	ARG_SUCCESS	0	/* processing successful */
64 #define	ARG_BAD		1	/* argument had a bad value */
65 
66 typedef struct umem_env_item {
67 	const char *item_name;	/* tag in environment variable */
68 	const char *item_interface_stability;
69 	enum {
70 	    ITEM_INVALID,
71 	    ITEM_FLAG,		/* only a flag.  No argument allowed */
72 	    ITEM_CLEARFLAG,	/* only a flag, but clear instead of set */
73 	    ITEM_OPTUINT,	/* optional integer argument */
74 	    ITEM_UINT,		/* required integer argument */
75 	    ITEM_OPTSIZE,	/* optional size_t argument */
76 	    ITEM_SIZE,		/* required size_t argument */
77 	    ITEM_SPECIAL	/* special argument processing */
78 	} item_type;
79 	const char *item_description;
80 	uint_t *item_flag_target; /* the variable containing the flag */
81 	uint_t item_flag_value;	/* the value to OR in */
82 	uint_t *item_uint_target; /* the variable to hold the integer */
83 	size_t *item_size_target;
84 	arg_process_t *item_special; /* callback for special handling */
85 } umem_env_item_t;
86 
87 #ifndef UMEM_STANDALONE
88 static arg_process_t umem_backend_process;
89 static arg_process_t umem_allocator_process;
90 #endif
91 
92 static arg_process_t umem_log_process;
93 
94 static size_t umem_size_tempval;
95 static arg_process_t umem_size_process;
96 
97 const char *____umem_environ_msg_options = "-- UMEM_OPTIONS --";
98 
99 static umem_env_item_t umem_options_items[] = {
100 #ifndef UMEM_STANDALONE
101 	{ "backend",		"Evolving",	ITEM_SPECIAL,
102 		"=sbrk for sbrk(2), =mmap for mmap(2)",
103 		NULL, 0, NULL, NULL,
104 		&umem_backend_process
105 	},
106 	{ "allocator",		"Evolving",	ITEM_SPECIAL,
107 		"=best, =first, =next, or =instant",
108 		NULL, 0, NULL, NULL,
109 		&umem_allocator_process
110 	},
111 #endif
112 
113 	{ "concurrency",	"Private",	ITEM_UINT,
114 		"Max concurrency",
115 		NULL, 0,	&umem_max_ncpus
116 	},
117 	{ "max_contention",	"Private",	ITEM_UINT,
118 		"Maximum contention in a reap interval before the depot is "
119 		    "resized.",
120 		NULL, 0,	&umem_depot_contention
121 	},
122 	{ "nomagazines",	"Private",	ITEM_FLAG,
123 		"no caches will be multithreaded, and no caching will occur.",
124 		&umem_flags,	UMF_NOMAGAZINE
125 	},
126 	{ "reap_interval",	"Private",	ITEM_UINT,
127 		"Minimum time between reaps and updates, in seconds.",
128 		NULL, 0,	&umem_reap_interval
129 	},
130 
131 	{ "size_add",		"Private",	ITEM_SPECIAL,
132 		"add a size to the cache size table",
133 		NULL, 0, NULL,
134 		&umem_size_tempval,		&umem_size_process
135 	},
136 	{ "size_clear",		"Private",	ITEM_SPECIAL,
137 		"clear all but the largest size from the cache size table",
138 		NULL, 0, NULL,
139 		&umem_size_tempval,		&umem_size_process
140 	},
141 	{ "size_remove",	"Private",	ITEM_SPECIAL,
142 	    "remove a size from the cache size table",
143 		NULL, 0, NULL,
144 		&umem_size_tempval,		&umem_size_process
145 	},
146 
147 #ifndef UMEM_STANDALONE
148 	{ "sbrk_minalloc",	"Private",	ITEM_SIZE,
149 		"The minimum allocation chunk for the sbrk(2) heap.",
150 		NULL, 0, NULL,	&vmem_sbrk_minalloc
151 	},
152 	{ "sbrk_pagesize",	"Private",	ITEM_SIZE,
153 		"The preferred page size for the sbrk(2) heap.",
154 		NULL, 0, NULL,	&vmem_sbrk_pagesize
155 	},
156 #endif
157 	{ "perthread_cache",	"Evolving",	ITEM_SIZE,
158 		"Size (in bytes) of per-thread allocation cache",
159 		NULL, 0, NULL, &umem_ptc_size
160 	},
161 	{ NULL, "-- end of UMEM_OPTIONS --",	ITEM_INVALID }
162 };
163 
164 const char *____umem_environ_msg_debug = "-- UMEM_DEBUG --";
165 
166 static umem_env_item_t umem_debug_items[] = {
167 	{ "default",		"Unstable",	ITEM_FLAG,
168 		"audit,contents,guards",
169 		&umem_flags,
170 		UMF_AUDIT | UMF_CONTENTS | UMF_DEADBEEF | UMF_REDZONE
171 	},
172 	{ "audit",		"Unstable",	ITEM_OPTUINT,
173 		"Enable auditing.  optionally =frames to set the number of "
174 		    "stored stack frames",
175 		&umem_flags,	UMF_AUDIT,	&umem_stack_depth
176 	},
177 	{ "contents",		"Unstable",	ITEM_OPTSIZE,
178 		"Enable contents storing.  UMEM_LOGGING=contents also "
179 		    "required.  optionally =bytes to set the number of stored "
180 		    "bytes",
181 		&umem_flags,	UMF_CONTENTS, NULL,	&umem_content_maxsave
182 	},
183 	{ "guards",		"Unstable",	ITEM_FLAG,
184 		"Enables guards and special patterns",
185 		&umem_flags,	UMF_DEADBEEF | UMF_REDZONE
186 	},
187 	{ "verbose",		"Unstable",	ITEM_FLAG,
188 		"Enables writing error messages to stderr",
189 		&umem_output,	1
190 	},
191 
192 	{ "nosignal",	"Private",	ITEM_FLAG,
193 		"Abort if called from a signal handler.  Turns on 'audit'.  "
194 		    "Note that this is not always a bug.",
195 		&umem_flags,	UMF_AUDIT | UMF_CHECKSIGNAL
196 	},
197 	{ "firewall",		"Private",	ITEM_SIZE,
198 		"=minbytes.  Every object >= minbytes in size will have its "
199 		    "end against an unmapped page",
200 		&umem_flags,	UMF_FIREWALL,	NULL,	&umem_minfirewall
201 	},
202 	{ "lite",		"Private",	ITEM_FLAG,
203 		"debugging-lite",
204 		&umem_flags,	UMF_LITE
205 	},
206 	{ "maxverify",		"Private",	ITEM_SIZE,
207 		"=maxbytes, Maximum bytes to check when 'guards' is active. "
208 		    "Normally all bytes are checked.",
209 		NULL, 0, NULL,	&umem_maxverify
210 	},
211 	{ "noabort",		"Private",	ITEM_CLEARFLAG,
212 		"umem will not abort when a recoverable error occurs "
213 		    "(i.e. double frees, certain kinds of corruption)",
214 		&umem_abort,	1
215 	},
216 	{ "mtbf",		"Private",	ITEM_UINT,
217 		"=mtbf, the mean time between injected failures.  Works best "
218 		    "if prime.\n",
219 		NULL, 0,	&umem_mtbf
220 	},
221 	{ "random",		"Private",	ITEM_FLAG,
222 		"randomize flags on a per-cache basis",
223 		&umem_flags,	UMF_RANDOMIZE
224 	},
225 	{ "allverbose",		"Private",	ITEM_FLAG,
226 		"Enables writing all logged messages to stderr",
227 		&umem_output,	2
228 	},
229 
230 	{ NULL, "-- end of UMEM_DEBUG --",	ITEM_INVALID }
231 };
232 
233 const char *____umem_environ_msg_logging = "-- UMEM_LOGGING --";
234 
235 static umem_env_item_t umem_logging_items[] = {
236 	{ "transaction",	"Unstable",	ITEM_SPECIAL,
237 		"If 'audit' is set in UMEM_DEBUG, the audit structures "
238 		    "from previous transactions are entered into this log.",
239 		NULL, 0, NULL,
240 		&umem_transaction_log_size,	&umem_log_process
241 	},
242 	{ "contents",		"Unstable",	ITEM_SPECIAL,
243 		"If 'audit' is set in UMEM_DEBUG, the contents of objects "
244 		    "are recorded in this log as they are freed.  If the "
245 		    "'contents' option is not set in UMEM_DEBUG, the first "
246 		    "256 bytes of each freed buffer will be saved.",
247 		&umem_flags,	UMF_CONTENTS,	NULL,
248 		&umem_content_log_size,		&umem_log_process
249 	},
250 	{ "fail",		"Unstable",	ITEM_SPECIAL,
251 		"Records are entered into this log for every failed "
252 		    "allocation.",
253 		NULL, 0, NULL,
254 		&umem_failure_log_size,		&umem_log_process
255 	},
256 
257 	{ "slab",		"Private",	ITEM_SPECIAL,
258 		"Every slab created will be entered into this log.",
259 		NULL, 0, NULL,
260 		&umem_slab_log_size,		&umem_log_process
261 	},
262 
263 	{ NULL, "-- end of UMEM_LOGGING --",	ITEM_INVALID }
264 };
265 
266 typedef struct umem_envvar {
267 	const char *env_name;
268 	const char *env_func;
269 	umem_env_item_t	*env_item_list;
270 	const char *env_getenv_result;
271 	const char *env_func_result;
272 } umem_envvar_t;
273 
274 static umem_envvar_t umem_envvars[] = {
275 	{ "UMEM_DEBUG",		"_umem_debug_init",	umem_debug_items },
276 	{ "UMEM_OPTIONS",	"_umem_options_init",	umem_options_items },
277 	{ "UMEM_LOGGING",	"_umem_logging_init",	umem_logging_items },
278 	{ NULL, NULL, NULL }
279 };
280 
281 static umem_envvar_t *env_current;
282 #define	CURRENT		(env_current->env_name)
283 
284 static int
285 empty(const char *str)
286 {
287 	char c;
288 
289 	while ((c = *str) != '\0' && isspace(c))
290 		str++;
291 
292 	return (*str == '\0');
293 }
294 
295 static int
296 item_uint_process(const umem_env_item_t *item, const char *item_arg)
297 {
298 	ulong_t result;
299 	char *endptr = "";
300 	int olderrno;
301 
302 	olderrno = errno;
303 	errno = 0;
304 
305 	if (empty(item_arg)) {
306 		goto badnumber;
307 	}
308 
309 	result = strtoul(item_arg, &endptr, 10);
310 
311 	if (result == ULONG_MAX && errno == ERANGE) {
312 		errno = olderrno;
313 		goto overflow;
314 	}
315 	errno = olderrno;
316 
317 	if (*endptr != '\0')
318 		goto badnumber;
319 	if ((uint_t)result != result)
320 		goto overflow;
321 
322 	(*item->item_uint_target) = (uint_t)result;
323 	return (ARG_SUCCESS);
324 
325 badnumber:
326 	log_message("%s: %s: not a number\n", CURRENT, item->item_name);
327 	return (ARG_BAD);
328 
329 overflow:
330 	log_message("%s: %s: overflowed\n", CURRENT, item->item_name);
331 	return (ARG_BAD);
332 }
333 
334 static int
335 item_size_process(const umem_env_item_t *item, const char *item_arg)
336 {
337 	ulong_t result;
338 	ulong_t result_arg;
339 	char *endptr = "";
340 	int olderrno;
341 
342 	if (empty(item_arg))
343 		goto badnumber;
344 
345 	olderrno = errno;
346 	errno = 0;
347 
348 	result_arg = strtoul(item_arg, &endptr, 10);
349 
350 	if (result_arg == ULONG_MAX && errno == ERANGE) {
351 		errno = olderrno;
352 		goto overflow;
353 	}
354 	errno = olderrno;
355 
356 	result = result_arg;
357 
358 	switch (*endptr) {
359 	case 't':
360 	case 'T':
361 		result *= 1024;
362 		if (result < result_arg)
363 			goto overflow;
364 		/*FALLTHRU*/
365 	case 'g':
366 	case 'G':
367 		result *= 1024;
368 		if (result < result_arg)
369 			goto overflow;
370 		/*FALLTHRU*/
371 	case 'm':
372 	case 'M':
373 		result *= 1024;
374 		if (result < result_arg)
375 			goto overflow;
376 		/*FALLTHRU*/
377 	case 'k':
378 	case 'K':
379 		result *= 1024;
380 		if (result < result_arg)
381 			goto overflow;
382 		endptr++;		/* skip over the size character */
383 		break;
384 	default:
385 		break;			/* handled later */
386 	}
387 
388 	if (*endptr != '\0')
389 		goto badnumber;
390 
391 	(*item->item_size_target) = result;
392 	return (ARG_SUCCESS);
393 
394 badnumber:
395 	log_message("%s: %s: not a number\n", CURRENT, item->item_name);
396 	return (ARG_BAD);
397 
398 overflow:
399 	log_message("%s: %s: overflowed\n", CURRENT, item->item_name);
400 	return (ARG_BAD);
401 }
402 
403 static int
404 umem_log_process(const umem_env_item_t *item, const char *item_arg)
405 {
406 	if (item_arg != NULL) {
407 		int ret;
408 		ret = item_size_process(item, item_arg);
409 		if (ret != ARG_SUCCESS)
410 			return (ret);
411 
412 		if (*item->item_size_target == 0)
413 			return (ARG_SUCCESS);
414 	} else
415 		*item->item_size_target = 64*1024;
416 
417 	umem_logging = 1;
418 	return (ARG_SUCCESS);
419 }
420 
421 static int
422 umem_size_process(const umem_env_item_t *item, const char *item_arg)
423 {
424 	const char *name = item->item_name;
425 	void (*action_func)(size_t);
426 
427 	size_t result;
428 
429 	int ret;
430 
431 	if (strcmp(name, "size_clear") == 0) {
432 		if (item_arg != NULL) {
433 			log_message("%s: %s: does not take a value. ignored\n",
434 			    CURRENT, name);
435 			return (ARG_BAD);
436 		}
437 		umem_alloc_sizes_clear();
438 		return (ARG_SUCCESS);
439 	} else if (strcmp(name, "size_add") == 0) {
440 		action_func = umem_alloc_sizes_add;
441 	} else if (strcmp(name, "size_remove") == 0) {
442 		action_func = umem_alloc_sizes_remove;
443 	} else {
444 		log_message("%s: %s: internally unrecognized\n",
445 		    CURRENT, name, name, name);
446 		return (ARG_BAD);
447 	}
448 
449 	if (item_arg == NULL) {
450 		log_message("%s: %s: requires a value. ignored\n",
451 		    CURRENT, name);
452 		return (ARG_BAD);
453 	}
454 
455 	ret = item_size_process(item, item_arg);
456 	if (ret != ARG_SUCCESS)
457 		return (ret);
458 
459 	result = *item->item_size_target;
460 	action_func(result);
461 	return (ARG_SUCCESS);
462 }
463 
464 #ifndef UMEM_STANDALONE
465 static int
466 umem_backend_process(const umem_env_item_t *item, const char *item_arg)
467 {
468 	const char *name = item->item_name;
469 
470 	if (item_arg == NULL)
471 		goto fail;
472 
473 	if (strcmp(item_arg, "sbrk") == 0)
474 		vmem_backend |= VMEM_BACKEND_SBRK;
475 	else if (strcmp(item_arg, "mmap") == 0)
476 		vmem_backend |= VMEM_BACKEND_MMAP;
477 	else
478 		goto fail;
479 
480 	return (ARG_SUCCESS);
481 
482 fail:
483 	log_message("%s: %s: must be %s=sbrk or %s=mmap\n",
484 	    CURRENT, name, name, name);
485 	return (ARG_BAD);
486 }
487 
488 
489 static int
490 umem_allocator_process(const umem_env_item_t *item, const char *item_arg)
491 {
492 	const char *name = item->item_name;
493 
494 	if (item_arg == NULL)
495 		goto fail;
496 
497 	if (strcmp(item_arg, "best") == 0)
498 		vmem_allocator = VM_BESTFIT;
499 	else if (strcmp(item_arg, "next") == 0)
500 		vmem_allocator = VM_NEXTFIT;
501 	else if (strcmp(item_arg, "first") == 0)
502 		vmem_allocator = VM_FIRSTFIT;
503 	else if (strcmp(item_arg, "instant") == 0)
504 		vmem_allocator = 0;
505 	else
506 		goto fail;
507 
508 	return (ARG_SUCCESS);
509 
510 fail:
511 	log_message("%s: %s: must be %s=best, %s=next or %s=first\n",
512 	    CURRENT, name, name, name, name);
513 	return (ARG_BAD);
514 
515 }
516 #endif
517 
518 static int
519 process_item(const umem_env_item_t *item, const char *item_arg)
520 {
521 	int arg_required = 0;
522 	arg_process_t *processor;
523 
524 	switch (item->item_type) {
525 	case ITEM_FLAG:
526 	case ITEM_CLEARFLAG:
527 	case ITEM_OPTUINT:
528 	case ITEM_OPTSIZE:
529 	case ITEM_SPECIAL:
530 		arg_required = 0;
531 		break;
532 
533 	case ITEM_UINT:
534 	case ITEM_SIZE:
535 		arg_required = 1;
536 		break;
537 	}
538 
539 	switch (item->item_type) {
540 	case ITEM_FLAG:
541 	case ITEM_CLEARFLAG:
542 		if (item_arg != NULL) {
543 			log_message("%s: %s: does not take a value. ignored\n",
544 			    CURRENT, item->item_name);
545 			return (1);
546 		}
547 		processor = NULL;
548 		break;
549 
550 	case ITEM_UINT:
551 	case ITEM_OPTUINT:
552 		processor = item_uint_process;
553 		break;
554 
555 	case ITEM_SIZE:
556 	case ITEM_OPTSIZE:
557 		processor = item_size_process;
558 		break;
559 
560 	case ITEM_SPECIAL:
561 		processor = item->item_special;
562 		break;
563 
564 	default:
565 		log_message("%s: %s: Invalid type.  Ignored\n",
566 		    CURRENT, item->item_name);
567 		return (1);
568 	}
569 
570 	if (arg_required && item_arg == NULL) {
571 		log_message("%s: %s: Required value missing\n",
572 		    CURRENT, item->item_name);
573 		goto invalid;
574 	}
575 
576 	if (item_arg != NULL || item->item_type == ITEM_SPECIAL) {
577 		if (processor(item, item_arg) != ARG_SUCCESS)
578 			goto invalid;
579 	}
580 
581 	if (item->item_flag_target) {
582 		if (item->item_type == ITEM_CLEARFLAG)
583 			(*item->item_flag_target) &= ~item->item_flag_value;
584 		else
585 			(*item->item_flag_target) |= item->item_flag_value;
586 	}
587 	return (0);
588 
589 invalid:
590 	return (1);
591 }
592 
593 #define	ENV_SHORT_BYTES	10	/* bytes to print on error */
594 void
595 umem_process_value(umem_env_item_t *item_list, const char *beg, const char *end)
596 {
597 	char buf[UMEM_ENV_ITEM_MAX];
598 	char *argptr;
599 
600 	size_t count;
601 
602 	while (beg < end && isspace(*beg))
603 		beg++;
604 
605 	while (beg < end && isspace(*(end - 1)))
606 		end--;
607 
608 	if (beg >= end) {
609 		log_message("%s: empty option\n", CURRENT);
610 		return;
611 	}
612 
613 	count = end - beg;
614 
615 	if (count + 1 > sizeof (buf)) {
616 		char outbuf[ENV_SHORT_BYTES + 1];
617 		/*
618 		 * Have to do this, since sprintf("%10s",...) calls malloc()
619 		 */
620 		(void) strncpy(outbuf, beg, ENV_SHORT_BYTES);
621 		outbuf[ENV_SHORT_BYTES] = 0;
622 
623 		log_message("%s: argument \"%s...\" too long\n", CURRENT,
624 		    outbuf);
625 		return;
626 	}
627 
628 	(void) strncpy(buf, beg, count);
629 	buf[count] = 0;
630 
631 	argptr = strchr(buf, '=');
632 
633 	if (argptr != NULL)
634 		*argptr++ = 0;
635 
636 	for (; item_list->item_name != NULL; item_list++) {
637 		if (strcmp(buf, item_list->item_name) == 0) {
638 			(void) process_item(item_list, argptr);
639 			return;
640 		}
641 	}
642 	log_message("%s: '%s' not recognized\n", CURRENT, buf);
643 }
644 
645 /*ARGSUSED*/
646 void
647 umem_setup_envvars(int invalid)
648 {
649 	umem_envvar_t *cur_env;
650 	static volatile enum {
651 		STATE_START,
652 		STATE_GETENV,
653 		STATE_DLOPEN,
654 		STATE_DLSYM,
655 		STATE_FUNC,
656 		STATE_DONE
657 	} state = STATE_START;
658 #ifndef UMEM_STANDALONE
659 	void *h;
660 #endif
661 
662 	if (invalid) {
663 		const char *where;
664 		/*
665 		 * One of the calls below invoked malloc() recursively.  We
666 		 * remove any partial results and return.
667 		 */
668 
669 		switch (state) {
670 		case STATE_START:
671 			where = "before getenv(3C) calls -- "
672 			    "getenv(3C) results ignored.";
673 			break;
674 		case STATE_GETENV:
675 			where = "during getenv(3C) calls -- "
676 			    "getenv(3C) results ignored.";
677 			break;
678 		case STATE_DLOPEN:
679 			where = "during dlopen(3C) call -- "
680 			    "_umem_*() results ignored.";
681 			break;
682 		case STATE_DLSYM:
683 			where = "during dlsym(3C) call -- "
684 			    "_umem_*() results ignored.";
685 			break;
686 		case STATE_FUNC:
687 			where = "during _umem_*() call -- "
688 			    "_umem_*() results ignored.";
689 			break;
690 		case STATE_DONE:
691 			where = "after dlsym() or _umem_*() calls.";
692 			break;
693 		default:
694 			where = "at unknown point -- "
695 			    "_umem_*() results ignored.";
696 			break;
697 		}
698 
699 		log_message("recursive allocation %s\n", where);
700 
701 		for (cur_env = umem_envvars; cur_env->env_name != NULL;
702 		    cur_env++) {
703 			if (state == STATE_GETENV)
704 				cur_env->env_getenv_result = NULL;
705 			if (state != STATE_DONE)
706 				cur_env->env_func_result = NULL;
707 		}
708 
709 		state = STATE_DONE;
710 		return;
711 	}
712 
713 	state = STATE_GETENV;
714 
715 	for (cur_env = umem_envvars; cur_env->env_name != NULL; cur_env++) {
716 		cur_env->env_getenv_result = getenv(cur_env->env_name);
717 		if (state == STATE_DONE)
718 			return;		/* recursed */
719 	}
720 
721 #ifndef UMEM_STANDALONE
722 	state = STATE_DLOPEN;
723 
724 	/* get a handle to the "a.out" object */
725 	if ((h = dlopen(0, RTLD_FIRST | RTLD_LAZY)) != NULL) {
726 		for (cur_env = umem_envvars; cur_env->env_name != NULL;
727 		    cur_env++) {
728 			const char *(*func)(void);
729 			const char *value;
730 
731 			state = STATE_DLSYM;
732 			func = (const char *(*)(void))dlsym(h,
733 			    cur_env->env_func);
734 
735 			if (state == STATE_DONE)
736 				break;		/* recursed */
737 
738 			state = STATE_FUNC;
739 			if (func != NULL) {
740 				value = func();
741 				if (state == STATE_DONE)
742 					break;		/* recursed */
743 				cur_env->env_func_result = value;
744 			}
745 		}
746 		(void) dlclose(h);
747 	} else {
748 		(void) dlerror();		/* snarf dlerror() */
749 	}
750 #endif /* UMEM_STANDALONE */
751 
752 	state = STATE_DONE;
753 }
754 
755 /*
756  * Process the environment variables.
757  */
758 void
759 umem_process_envvars(void)
760 {
761 	const char *value;
762 	const char *end, *next;
763 	umem_envvar_t *cur_env;
764 
765 	for (cur_env = umem_envvars; cur_env->env_name != NULL; cur_env++) {
766 		env_current = cur_env;
767 
768 		value = cur_env->env_getenv_result;
769 		if (value == NULL)
770 			value = cur_env->env_func_result;
771 
772 		/* ignore if missing or empty */
773 		if (value == NULL)
774 			continue;
775 
776 		for (end = value; *end != '\0'; value = next) {
777 			end = strchr(value, ',');
778 			if (end != NULL)
779 				next = end + 1;		/* skip the comma */
780 			else
781 				next = end = value + strlen(value);
782 
783 			umem_process_value(cur_env->env_item_list, value, end);
784 		}
785 	}
786 }
787