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