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 * 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 544 switch (item->item_type) { 545 case ITEM_FLAG: 546 case ITEM_CLEARFLAG: 547 if (item_arg != NULL) { 548 log_message("%s: %s: does not take a value. ignored\n", 549 CURRENT, item->item_name); 550 return (1); 551 } 552 processor = NULL; 553 break; 554 555 case ITEM_UINT: 556 case ITEM_OPTUINT: 557 processor = item_uint_process; 558 break; 559 560 case ITEM_SIZE: 561 case ITEM_OPTSIZE: 562 processor = item_size_process; 563 break; 564 565 case ITEM_SPECIAL: 566 processor = item->item_special; 567 break; 568 569 default: 570 log_message("%s: %s: Invalid type. Ignored\n", 571 CURRENT, item->item_name); 572 return (1); 573 } 574 575 if (arg_required && item_arg == NULL) { 576 log_message("%s: %s: Required value missing\n", 577 CURRENT, item->item_name); 578 goto invalid; 579 } 580 581 if (item_arg != NULL || item->item_type == ITEM_SPECIAL) { 582 if (processor(item, item_arg) != ARG_SUCCESS) 583 goto invalid; 584 } 585 586 if (item->item_flag_target) { 587 if (item->item_type == ITEM_CLEARFLAG) 588 (*item->item_flag_target) &= ~item->item_flag_value; 589 else 590 (*item->item_flag_target) |= item->item_flag_value; 591 } 592 return (0); 593 594 invalid: 595 return (1); 596 } 597 598 #define ENV_SHORT_BYTES 10 /* bytes to print on error */ 599 void 600 umem_process_value(umem_env_item_t *item_list, const char *beg, const char *end) 601 { 602 char buf[UMEM_ENV_ITEM_MAX]; 603 char *argptr; 604 605 size_t count; 606 607 while (beg < end && isspace(*beg)) 608 beg++; 609 610 while (beg < end && isspace(*(end - 1))) 611 end--; 612 613 if (beg >= end) { 614 log_message("%s: empty option\n", CURRENT); 615 return; 616 } 617 618 count = end - beg; 619 620 if (count + 1 > sizeof (buf)) { 621 char outbuf[ENV_SHORT_BYTES + 1]; 622 /* 623 * Have to do this, since sprintf("%10s",...) calls malloc() 624 */ 625 (void) strncpy(outbuf, beg, ENV_SHORT_BYTES); 626 outbuf[ENV_SHORT_BYTES] = 0; 627 628 log_message("%s: argument \"%s...\" too long\n", CURRENT, 629 outbuf); 630 return; 631 } 632 633 (void) strncpy(buf, beg, count); 634 buf[count] = 0; 635 636 argptr = strchr(buf, '='); 637 638 if (argptr != NULL) 639 *argptr++ = 0; 640 641 for (; item_list->item_name != NULL; item_list++) { 642 if (strcmp(buf, item_list->item_name) == 0) { 643 (void) process_item(item_list, argptr); 644 return; 645 } 646 } 647 log_message("%s: '%s' not recognized\n", CURRENT, buf); 648 } 649 650 /*ARGSUSED*/ 651 void 652 umem_setup_envvars(int invalid) 653 { 654 umem_envvar_t *cur_env; 655 static volatile enum { 656 STATE_START, 657 STATE_GETENV, 658 STATE_DLOPEN, 659 STATE_DLSYM, 660 STATE_FUNC, 661 STATE_DONE 662 } state = STATE_START; 663 #ifndef UMEM_STANDALONE 664 void *h; 665 #endif 666 667 if (invalid) { 668 const char *where; 669 /* 670 * One of the calls below invoked malloc() recursively. We 671 * remove any partial results and return. 672 */ 673 674 switch (state) { 675 case STATE_START: 676 where = "before getenv(3C) calls -- " 677 "getenv(3C) results ignored."; 678 break; 679 case STATE_GETENV: 680 where = "during getenv(3C) calls -- " 681 "getenv(3C) results ignored."; 682 break; 683 case STATE_DLOPEN: 684 where = "during dlopen(3C) call -- " 685 "_umem_*() results ignored."; 686 break; 687 case STATE_DLSYM: 688 where = "during dlsym(3C) call -- " 689 "_umem_*() results ignored."; 690 break; 691 case STATE_FUNC: 692 where = "during _umem_*() call -- " 693 "_umem_*() results ignored."; 694 break; 695 case STATE_DONE: 696 where = "after dlsym() or _umem_*() calls."; 697 break; 698 default: 699 where = "at unknown point -- " 700 "_umem_*() results ignored."; 701 break; 702 } 703 704 log_message("recursive allocation %s\n", where); 705 706 for (cur_env = umem_envvars; cur_env->env_name != NULL; 707 cur_env++) { 708 if (state == STATE_GETENV) 709 cur_env->env_getenv_result = NULL; 710 if (state != STATE_DONE) 711 cur_env->env_func_result = NULL; 712 } 713 714 state = STATE_DONE; 715 return; 716 } 717 718 state = STATE_GETENV; 719 720 for (cur_env = umem_envvars; cur_env->env_name != NULL; cur_env++) { 721 cur_env->env_getenv_result = getenv(cur_env->env_name); 722 if (state == STATE_DONE) 723 return; /* recursed */ 724 } 725 726 #ifndef UMEM_STANDALONE 727 state = STATE_DLOPEN; 728 729 /* get a handle to the "a.out" object */ 730 if ((h = dlopen(0, RTLD_FIRST | RTLD_LAZY)) != NULL) { 731 for (cur_env = umem_envvars; cur_env->env_name != NULL; 732 cur_env++) { 733 const char *(*func)(void); 734 const char *value; 735 736 state = STATE_DLSYM; 737 func = (const char *(*)(void))dlsym(h, 738 cur_env->env_func); 739 740 if (state == STATE_DONE) 741 break; /* recursed */ 742 743 state = STATE_FUNC; 744 if (func != NULL) { 745 value = func(); 746 if (state == STATE_DONE) 747 break; /* recursed */ 748 cur_env->env_func_result = value; 749 } 750 } 751 (void) dlclose(h); 752 } else { 753 (void) dlerror(); /* snarf dlerror() */ 754 } 755 #endif /* UMEM_STANDALONE */ 756 757 state = STATE_DONE; 758 } 759 760 /* 761 * Process the environment variables. 762 */ 763 void 764 umem_process_envvars(void) 765 { 766 const char *value; 767 const char *end, *next; 768 umem_envvar_t *cur_env; 769 770 for (cur_env = umem_envvars; cur_env->env_name != NULL; cur_env++) { 771 env_current = cur_env; 772 773 value = cur_env->env_getenv_result; 774 if (value == NULL) 775 value = cur_env->env_func_result; 776 777 /* ignore if missing or empty */ 778 if (value == NULL) 779 continue; 780 781 for (end = value; *end != '\0'; value = next) { 782 end = strchr(value, ','); 783 if (end != NULL) 784 next = end + 1; /* skip the comma */ 785 else 786 next = end = value + strlen(value); 787 788 umem_process_value(cur_env->env_item_list, value, end); 789 } 790 } 791 } 792