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 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 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 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 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 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 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 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 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 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 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 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