/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Portions Copyright 2012 Joyent, Inc. All rights reserved. * * Portions Copyright 2006-2008 Message Systems, Inc. All rights reserved. */ /* #pragma ident "@(#)envvar.c 1.5 05/06/08 SMI" */ #include "config.h" #include #include #include #include #include #if HAVE_DLFCN_H #include #endif #include "umem_base.h" #include "vmem_base.h" /* * A umem environment variable, like UMEM_DEBUG, is set to a series * of items, seperated by ',': * * UMEM_DEBUG="audit=10,guards,firewall=512" * * This structure describes items. Each item has a name, type, and * description. During processing, an item read from the user may * be either "valid" or "invalid". * * A valid item has an argument, if required, and it is of the right * form (doesn't overflow, doesn't contain any unexpected characters). * * If the item is valid, item_flag_target != NULL, and: * type is not CLEARFLAG, then (*item_flag_target) |= item_flag_value * type is CLEARFLAG, then (*item_flag_target) &= ~item_flag_value */ #define UMEM_ENV_ITEM_MAX 512 struct umem_env_item; typedef int arg_process_t(const struct umem_env_item *item, const char *value); #define ARG_SUCCESS 0 /* processing successful */ #define ARG_BAD 1 /* argument had a bad value */ typedef struct umem_env_item { const char *item_name; /* tag in environment variable */ const char *item_interface_stability; enum { ITEM_INVALID, ITEM_FLAG, /* only a flag. No argument allowed */ ITEM_CLEARFLAG, /* only a flag, but clear instead of set */ ITEM_OPTUINT, /* optional integer argument */ ITEM_UINT, /* required integer argument */ ITEM_OPTSIZE, /* optional size_t argument */ ITEM_SIZE, /* required size_t argument */ ITEM_SPECIAL /* special argument processing */ } item_type; const char *item_description; uint_t *item_flag_target; /* the variable containing the flag */ uint_t item_flag_value; /* the value to OR in */ uint_t *item_uint_target; /* the variable to hold the integer */ size_t *item_size_target; arg_process_t *item_special; /* callback for special handling */ } umem_env_item_t; #ifndef UMEM_STANDALONE static arg_process_t umem_backend_process; static arg_process_t umem_allocator_process; #endif #ifdef __GLIBC__ /* replace getenv() with a specialized version that doesn't * need to allocate memory. We can't use strlen or strcmp * here. */ #include static char *safe_getenv(const char *name) { int i, l; for (l = 0; name[l]; l++) ; for (i = 0; __environ[i]; i++) { if (!memcmp(__environ[i], name, l) && __environ[i][l] == '=') { return &__environ[i][l+1]; } } return NULL; } #define getenv(x) safe_getenv(x) #endif static arg_process_t umem_log_process; static size_t umem_size_tempval; static arg_process_t umem_size_process; const char *____umem_environ_msg_options = "-- UMEM_OPTIONS --"; static umem_env_item_t umem_options_items[] = { #ifndef UMEM_STANDALONE { "backend", "Evolving", ITEM_SPECIAL, "=sbrk for sbrk(2), =mmap for mmap(2)", NULL, 0, NULL, NULL, &umem_backend_process }, { "allocator", "Evolving", ITEM_SPECIAL, "=best, =first, =next, or =instant", NULL, 0, NULL, NULL, &umem_allocator_process }, #endif { "concurrency", "Private", ITEM_UINT, "Max concurrency", NULL, 0, &umem_max_ncpus }, { "max_contention", "Private", ITEM_UINT, "Maximum contention in a reap interval before the depot is " "resized.", NULL, 0, &umem_depot_contention }, { "nomagazines", "Private", ITEM_FLAG, "no caches will be multithreaded, and no caching will occur.", &umem_flags, UMF_NOMAGAZINE }, { "reap_interval", "Private", ITEM_UINT, "Minimum time between reaps and updates, in seconds.", NULL, 0, &umem_reap_interval }, { "size_add", "Private", ITEM_SPECIAL, "add a size to the cache size table", NULL, 0, NULL, &umem_size_tempval, &umem_size_process }, { "size_clear", "Private", ITEM_SPECIAL, "clear all but the largest size from the cache size table", NULL, 0, NULL, &umem_size_tempval, &umem_size_process }, { "size_remove", "Private", ITEM_SPECIAL, "remove a size from the cache size table", NULL, 0, NULL, &umem_size_tempval, &umem_size_process }, #ifndef _WIN32 #ifndef UMEM_STANDALONE { "sbrk_minalloc", "Private", ITEM_SIZE, "The minimum allocation chunk for the sbrk(2) heap.", NULL, 0, NULL, &vmem_sbrk_minalloc }, { "sbrk_pagesize", "Private", ITEM_SIZE, "The preferred page size for the sbrk(2) heap.", NULL, 0, NULL, &vmem_sbrk_pagesize }, #endif #endif { "perthread_cache", "Evolving", ITEM_SIZE, "Size (in bytes) of per-thread allocation cache", NULL, 0, NULL, &umem_ptc_size }, { NULL, "-- end of UMEM_OPTIONS --", ITEM_INVALID } }; const char *____umem_environ_msg_debug = "-- UMEM_DEBUG --"; static umem_env_item_t umem_debug_items[] = { { "default", "Unstable", ITEM_FLAG, "audit,contents,guards", &umem_flags, UMF_AUDIT | UMF_CONTENTS | UMF_DEADBEEF | UMF_REDZONE }, { "audit", "Unstable", ITEM_OPTUINT, "Enable auditing. optionally =frames to set the number of " "stored stack frames", &umem_flags, UMF_AUDIT, &umem_stack_depth }, { "contents", "Unstable", ITEM_OPTSIZE, "Enable contents storing. UMEM_LOGGING=contents also " "required. optionally =bytes to set the number of stored " "bytes", &umem_flags, UMF_CONTENTS, NULL, &umem_content_maxsave }, { "guards", "Unstable", ITEM_FLAG, "Enables guards and special patterns", &umem_flags, UMF_DEADBEEF | UMF_REDZONE }, { "verbose", "Unstable", ITEM_FLAG, "Enables writing error messages to stderr", &umem_output, 1 }, { "nosignal", "Private", ITEM_FLAG, "Abort if called from a signal handler. Turns on 'audit'. " "Note that this is not always a bug.", &umem_flags, UMF_AUDIT | UMF_CHECKSIGNAL }, { "firewall", "Private", ITEM_SIZE, "=minbytes. Every object >= minbytes in size will have its " "end against an unmapped page", &umem_flags, UMF_FIREWALL, NULL, &umem_minfirewall }, { "lite", "Private", ITEM_FLAG, "debugging-lite", &umem_flags, UMF_LITE }, { "maxverify", "Private", ITEM_SIZE, "=maxbytes, Maximum bytes to check when 'guards' is active. " "Normally all bytes are checked.", NULL, 0, NULL, &umem_maxverify }, { "noabort", "Private", ITEM_CLEARFLAG, "umem will not abort when a recoverable error occurs " "(i.e. double frees, certain kinds of corruption)", &umem_abort, 1 }, { "mtbf", "Private", ITEM_UINT, "=mtbf, the mean time between injected failures. Works best " "if prime.\n", NULL, 0, &umem_mtbf }, { "random", "Private", ITEM_FLAG, "randomize flags on a per-cache basis", &umem_flags, UMF_RANDOMIZE }, { "allverbose", "Private", ITEM_FLAG, "Enables writing all logged messages to stderr", &umem_output, 2 }, { NULL, "-- end of UMEM_DEBUG --", ITEM_INVALID } }; const char *____umem_environ_msg_logging = "-- UMEM_LOGGING --"; static umem_env_item_t umem_logging_items[] = { { "transaction", "Unstable", ITEM_SPECIAL, "If 'audit' is set in UMEM_DEBUG, the audit structures " "from previous transactions are entered into this log.", NULL, 0, NULL, &umem_transaction_log_size, &umem_log_process }, { "contents", "Unstable", ITEM_SPECIAL, "If 'audit' is set in UMEM_DEBUG, the contents of objects " "are recorded in this log as they are freed. If the " "'contents' option is not set in UMEM_DEBUG, the first " "256 bytes of each freed buffer will be saved.", &umem_flags, UMF_CONTENTS, NULL, &umem_content_log_size, &umem_log_process }, { "fail", "Unstable", ITEM_SPECIAL, "Records are entered into this log for every failed " "allocation.", NULL, 0, NULL, &umem_failure_log_size, &umem_log_process }, { "slab", "Private", ITEM_SPECIAL, "Every slab created will be entered into this log.", NULL, 0, NULL, &umem_slab_log_size, &umem_log_process }, { NULL, "-- end of UMEM_LOGGING --", ITEM_INVALID } }; typedef struct umem_envvar { const char *env_name; const char *env_func; umem_env_item_t *env_item_list; const char *env_getenv_result; const char *env_func_result; } umem_envvar_t; static umem_envvar_t umem_envvars[] = { { "UMEM_DEBUG", "_umem_debug_init", umem_debug_items }, { "UMEM_OPTIONS", "_umem_options_init", umem_options_items }, { "UMEM_LOGGING", "_umem_logging_init", umem_logging_items }, { NULL, NULL, NULL } }; static umem_envvar_t *env_current; #define CURRENT (env_current->env_name) static int empty(const char *str) { char c; while ((c = *str) != '\0' && isspace(c)) str++; return (*str == '\0'); } static int item_uint_process(const umem_env_item_t *item, const char *item_arg) { ulong_t result; char *endptr = ""; int olderrno; olderrno = errno; errno = 0; if (empty(item_arg)) { goto badnumber; } result = strtoul(item_arg, &endptr, 10); if (result == ULONG_MAX && errno == ERANGE) { errno = olderrno; goto overflow; } errno = olderrno; if (*endptr != '\0') goto badnumber; if ((uint_t)result != result) goto overflow; (*item->item_uint_target) = (uint_t)result; return (ARG_SUCCESS); badnumber: log_message("%s: %s: not a number\n", CURRENT, item->item_name); return (ARG_BAD); overflow: log_message("%s: %s: overflowed\n", CURRENT, item->item_name); return (ARG_BAD); } static int item_size_process(const umem_env_item_t *item, const char *item_arg) { ulong_t result; ulong_t result_arg; char *endptr = ""; int olderrno; if (empty(item_arg)) goto badnumber; olderrno = errno; errno = 0; result_arg = strtoul(item_arg, &endptr, 10); if (result_arg == ULONG_MAX && errno == ERANGE) { errno = olderrno; goto overflow; } errno = olderrno; result = result_arg; switch (*endptr) { case 't': case 'T': result *= 1024; if (result < result_arg) goto overflow; /*FALLTHRU*/ case 'g': case 'G': result *= 1024; if (result < result_arg) goto overflow; /*FALLTHRU*/ case 'm': case 'M': result *= 1024; if (result < result_arg) goto overflow; /*FALLTHRU*/ case 'k': case 'K': result *= 1024; if (result < result_arg) goto overflow; endptr++; /* skip over the size character */ break; default: break; /* handled later */ } if (*endptr != '\0') goto badnumber; (*item->item_size_target) = result; return (ARG_SUCCESS); badnumber: log_message("%s: %s: not a number\n", CURRENT, item->item_name); return (ARG_BAD); overflow: log_message("%s: %s: overflowed\n", CURRENT, item->item_name); return (ARG_BAD); } static int umem_log_process(const umem_env_item_t *item, const char *item_arg) { if (item_arg != NULL) { int ret; ret = item_size_process(item, item_arg); if (ret != ARG_SUCCESS) return (ret); if (*item->item_size_target == 0) return (ARG_SUCCESS); } else *item->item_size_target = 64*1024; umem_logging = 1; return (ARG_SUCCESS); } static int umem_size_process(const umem_env_item_t *item, const char *item_arg) { const char *name = item->item_name; void (*action_func)(size_t); size_t result; int ret; if (strcmp(name, "size_clear") == 0) { if (item_arg != NULL) { log_message("%s: %s: does not take a value. ignored\n", CURRENT, name); return (ARG_BAD); } umem_alloc_sizes_clear(); return (ARG_SUCCESS); } else if (strcmp(name, "size_add") == 0) { action_func = umem_alloc_sizes_add; } else if (strcmp(name, "size_remove") == 0) { action_func = umem_alloc_sizes_remove; } else { log_message("%s: %s: internally unrecognized\n", CURRENT, name, name, name); return (ARG_BAD); } if (item_arg == NULL) { log_message("%s: %s: requires a value. ignored\n", CURRENT, name); return (ARG_BAD); } ret = item_size_process(item, item_arg); if (ret != ARG_SUCCESS) return (ret); result = *item->item_size_target; action_func(result); return (ARG_SUCCESS); } #ifndef UMEM_STANDALONE static int umem_backend_process(const umem_env_item_t *item, const char *item_arg) { const char *name = item->item_name; if (item_arg == NULL) goto fail; if (strcmp(item_arg, "sbrk") == 0) vmem_backend |= VMEM_BACKEND_SBRK; else if (strcmp(item_arg, "mmap") == 0) vmem_backend |= VMEM_BACKEND_MMAP; else goto fail; return (ARG_SUCCESS); fail: log_message("%s: %s: must be %s=sbrk or %s=mmap\n", CURRENT, name, name, name); return (ARG_BAD); } static int umem_allocator_process(const umem_env_item_t *item, const char *item_arg) { const char *name = item->item_name; if (item_arg == NULL) goto fail; if (strcmp(item_arg, "best") == 0) vmem_allocator = VM_BESTFIT; else if (strcmp(item_arg, "next") == 0) vmem_allocator = VM_NEXTFIT; else if (strcmp(item_arg, "first") == 0) vmem_allocator = VM_FIRSTFIT; else if (strcmp(item_arg, "instant") == 0) vmem_allocator = 0; else goto fail; return (ARG_SUCCESS); fail: log_message("%s: %s: must be %s=best, %s=next or %s=first\n", CURRENT, name, name, name, name); return (ARG_BAD); } #endif static int process_item(const umem_env_item_t *item, const char *item_arg) { int arg_required = 0; arg_process_t *processor; switch (item->item_type) { case ITEM_FLAG: case ITEM_CLEARFLAG: case ITEM_OPTUINT: case ITEM_OPTSIZE: case ITEM_SPECIAL: arg_required = 0; break; case ITEM_UINT: case ITEM_SIZE: arg_required = 1; break; case ITEM_INVALID: default: break; } switch (item->item_type) { case ITEM_FLAG: case ITEM_CLEARFLAG: if (item_arg != NULL) { log_message("%s: %s: does not take a value. ignored\n", CURRENT, item->item_name); return (1); } processor = NULL; break; case ITEM_UINT: case ITEM_OPTUINT: processor = item_uint_process; break; case ITEM_SIZE: case ITEM_OPTSIZE: processor = item_size_process; break; case ITEM_SPECIAL: processor = item->item_special; break; default: log_message("%s: %s: Invalid type. Ignored\n", CURRENT, item->item_name); return (1); } if (arg_required && item_arg == NULL) { log_message("%s: %s: Required value missing\n", CURRENT, item->item_name); goto invalid; } if (item_arg != NULL || item->item_type == ITEM_SPECIAL) { if (processor(item, item_arg) != ARG_SUCCESS) goto invalid; } if (item->item_flag_target) { if (item->item_type == ITEM_CLEARFLAG) (*item->item_flag_target) &= ~item->item_flag_value; else (*item->item_flag_target) |= item->item_flag_value; } return (0); invalid: return (1); } #define ENV_SHORT_BYTES 10 /* bytes to print on error */ void umem_process_value(umem_env_item_t *item_list, const char *beg, const char *end) { char buf[UMEM_ENV_ITEM_MAX]; char *argptr; size_t count; while (beg < end && isspace(*beg)) beg++; while (beg < end && isspace(*(end - 1))) end--; if (beg >= end) { log_message("%s: empty option\n", CURRENT); return; } count = end - beg; if (count + 1 > sizeof (buf)) { char outbuf[ENV_SHORT_BYTES + 1]; /* * Have to do this, since sprintf("%10s",...) calls malloc() */ (void) strncpy(outbuf, beg, ENV_SHORT_BYTES); outbuf[ENV_SHORT_BYTES] = 0; log_message("%s: argument \"%s...\" too long\n", CURRENT, outbuf); return; } (void) strncpy(buf, beg, count); buf[count] = 0; argptr = strchr(buf, '='); if (argptr != NULL) *argptr++ = 0; for (; item_list->item_name != NULL; item_list++) { if (strcmp(buf, item_list->item_name) == 0) { (void) process_item(item_list, argptr); return; } } log_message("%s: '%s' not recognized\n", CURRENT, buf); } /*ARGSUSED*/ void umem_setup_envvars(int invalid) { umem_envvar_t *cur_env; static volatile enum { STATE_START, STATE_GETENV, STATE_DLOPEN, STATE_DLSYM, STATE_FUNC, STATE_DONE } state = STATE_START; #ifndef UMEM_STANDALONE void *h; #endif if (invalid) { const char *where; /* * One of the calls below invoked malloc() recursively. We * remove any partial results and return. */ switch (state) { case STATE_START: where = "before getenv(3C) calls -- " "getenv(3C) results ignored."; break; case STATE_GETENV: where = "during getenv(3C) calls -- " "getenv(3C) results ignored."; break; case STATE_DLOPEN: where = "during dlopen(3C) call -- " "_umem_*() results ignored."; break; case STATE_DLSYM: where = "during dlsym(3C) call -- " "_umem_*() results ignored."; break; case STATE_FUNC: where = "during _umem_*() call -- " "_umem_*() results ignored."; break; case STATE_DONE: where = "after dlsym() or _umem_*() calls."; break; default: where = "at unknown point -- " "_umem_*() results ignored."; break; } log_message("recursive allocation %s\n", where); for (cur_env = umem_envvars; cur_env->env_name != NULL; cur_env++) { if (state == STATE_GETENV) cur_env->env_getenv_result = NULL; if (state != STATE_DONE) cur_env->env_func_result = NULL; } state = STATE_DONE; return; } state = STATE_GETENV; for (cur_env = umem_envvars; cur_env->env_name != NULL; cur_env++) { cur_env->env_getenv_result = getenv(cur_env->env_name); if (state == STATE_DONE) return; /* recursed */ } #ifndef UMEM_STANDALONE #ifdef _WIN32 # define dlopen(a, b) GetModuleHandle(NULL) # define dlsym(a, b) GetProcAddress((HANDLE)a, b) # define dlclose(a) 0 # define dlerror() 0 #endif state = STATE_DLOPEN; /* get a handle to the "a.out" object */ if ((h = dlopen(0, RTLD_FIRST | RTLD_LAZY)) != NULL) { for (cur_env = umem_envvars; cur_env->env_name != NULL; cur_env++) { const char *(*func)(void); const char *value; state = STATE_DLSYM; func = (const char *(*)(void))dlsym(h, cur_env->env_func); if (state == STATE_DONE) break; /* recursed */ state = STATE_FUNC; if (func != NULL) { value = func(); if (state == STATE_DONE) break; /* recursed */ cur_env->env_func_result = value; } } (void) dlclose(h); } else { (void) dlerror(); /* snarf dlerror() */ } #endif /* UMEM_STANDALONE */ state = STATE_DONE; } /* * Process the environment variables. */ void umem_process_envvars(void) { const char *value; const char *end, *next; umem_envvar_t *cur_env; for (cur_env = umem_envvars; cur_env->env_name != NULL; cur_env++) { env_current = cur_env; value = cur_env->env_getenv_result; if (value == NULL) value = cur_env->env_func_result; /* ignore if missing or empty */ if (value == NULL) continue; for (end = value; *end != '\0'; value = next) { end = strchr(value, ','); if (end != NULL) next = end + 1; /* skip the comma */ else next = end = value + strlen(value); umem_process_value(cur_env->env_item_list, value, end); } } }