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