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
empty(const char * str)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
item_uint_process(const umem_env_item_t * item,const char * item_arg)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
item_size_process(const umem_env_item_t * item,const char * item_arg)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
umem_log_process(const umem_env_item_t * item,const char * item_arg)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
umem_size_process(const umem_env_item_t * item,const char * item_arg)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
umem_backend_process(const umem_env_item_t * item,const char * item_arg)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
umem_allocator_process(const umem_env_item_t * item,const char * item_arg)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
process_item(const umem_env_item_t * item,const char * item_arg)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
umem_process_value(umem_env_item_t * item_list,const char * beg,const char * end)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
umem_setup_envvars(int invalid)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
umem_process_envvars(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