1 /* $NetBSD: subr_userconf.c,v 1.18 2005/12/11 12:24:30 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1996 Mats O Jansson <moj@stacken.kth.se> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by Mats O Jansson. 18 * 4. The name of the author may not be used to endorse or promote 19 * products derived from this software without specific prior written 20 * permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 23 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 26 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 * 34 * OpenBSD: subr_userconf.c,v 1.19 2000/01/08 23:23:37 d Exp 35 */ 36 37 #include <sys/cdefs.h> 38 __KERNEL_RCSID(0, "$NetBSD: subr_userconf.c,v 1.18 2005/12/11 12:24:30 christos Exp $"); 39 40 #include "opt_userconf.h" 41 42 #include <sys/param.h> 43 #include <sys/systm.h> 44 #include <sys/device.h> 45 #include <sys/malloc.h> 46 #include <sys/time.h> 47 48 #include <dev/cons.h> 49 50 extern struct cfdata cfdata[]; 51 52 static int userconf_base = 16; /* Base for "large" numbers */ 53 static int userconf_maxdev = -1; /* # of used device slots */ 54 static int userconf_totdev = -1; /* # of device slots */ 55 #if 0 56 static int userconf_maxlocnames = -1; /* # of locnames */ 57 #endif 58 static int userconf_cnt = -1; /* Line counter for ... */ 59 static int userconf_lines = 12; /* ... # of lines per page */ 60 static int userconf_histlen = 0; 61 static int userconf_histcur = 0; 62 static char userconf_history[1024]; 63 static int userconf_histsz = sizeof(userconf_history); 64 static char userconf_argbuf[40]; /* Additional input */ 65 static char userconf_cmdbuf[40]; /* Command line */ 66 static char userconf_histbuf[40]; 67 68 static int getsn(char *, int); 69 70 #define UC_CHANGE 'c' 71 #define UC_DISABLE 'd' 72 #define UC_ENABLE 'e' 73 #define UC_FIND 'f' 74 #define UC_SHOW 's' 75 76 static const char *userconf_cmds[] = { 77 "base", "b", 78 "change", "c", 79 "disable", "d", 80 "enable", "e", 81 "exit", "q", 82 "find", "f", 83 "help", "h", 84 "list", "l", 85 "lines", "L", 86 "quit", "q", 87 "?", "h", 88 "", "", 89 }; 90 91 static void 92 userconf_init(void) 93 { 94 int i; 95 struct cfdata *cf; 96 97 i = 0; 98 for (cf = cfdata; cf->cf_name; cf++) 99 i++; 100 101 userconf_maxdev = i - 1; 102 userconf_totdev = i - 1; 103 } 104 105 static int 106 userconf_more(void) 107 { 108 int quit = 0; 109 char c = '\0'; 110 111 if (userconf_cnt != -1) { 112 if (userconf_cnt == userconf_lines) { 113 printf("-- more --"); 114 c = cngetc(); 115 userconf_cnt = 0; 116 printf("\r \r"); 117 } 118 userconf_cnt++; 119 if (c == 'q' || c == 'Q') 120 quit = 1; 121 } 122 return (quit); 123 } 124 125 static void 126 userconf_hist_cmd(char cmd) 127 { 128 userconf_histcur = userconf_histlen; 129 if (userconf_histcur < userconf_histsz) { 130 userconf_history[userconf_histcur] = cmd; 131 userconf_histcur++; 132 } 133 } 134 135 static void 136 userconf_hist_int(int val) 137 { 138 snprintf(userconf_histbuf, sizeof(userconf_histbuf), " %d", val); 139 if ((userconf_histcur + strlen(userconf_histbuf)) < userconf_histsz) { 140 memcpy(&userconf_history[userconf_histcur], 141 userconf_histbuf, 142 strlen(userconf_histbuf)); 143 userconf_histcur = userconf_histcur + strlen(userconf_histbuf); 144 } 145 } 146 147 static void 148 userconf_hist_eoc(void) 149 { 150 if (userconf_histcur < userconf_histsz) { 151 userconf_history[userconf_histcur] = '\n'; 152 userconf_histcur++; 153 userconf_histlen = userconf_histcur; 154 } 155 } 156 157 static void 158 userconf_pnum(int val) 159 { 160 if (val > -2 && val < 16) { 161 printf("%d",val); 162 } else { 163 switch (userconf_base) { 164 case 8: 165 printf("0%o",val); 166 break; 167 case 10: 168 printf("%d",val); 169 break; 170 case 16: 171 default: 172 printf("0x%x",val); 173 break; 174 } 175 } 176 } 177 178 static void 179 userconf_pdevnam(short dev) 180 { 181 struct cfdata *cd; 182 183 cd = &cfdata[dev]; 184 printf("%s", cd->cf_name); 185 switch (cd->cf_fstate) { 186 case FSTATE_NOTFOUND: 187 case FSTATE_DNOTFOUND: 188 printf("%d", cd->cf_unit); 189 break; 190 case FSTATE_FOUND: 191 printf("*FOUND*"); 192 break; 193 case FSTATE_STAR: 194 case FSTATE_DSTAR: 195 printf("*"); 196 break; 197 default: 198 printf("*UNKNOWN*"); 199 break; 200 } 201 } 202 203 static void 204 userconf_pdev(short devno) 205 { 206 struct cfdata *cd; 207 const struct cfparent *cfp; 208 int *l; 209 const struct cfiattrdata *ia; 210 const struct cflocdesc *ld; 211 int nld, i; 212 213 if (devno > userconf_maxdev) { 214 printf("Unknown devno (max is %d)\n", userconf_maxdev); 215 return; 216 } 217 218 cd = &cfdata[devno]; 219 220 printf("[%3d] ", devno); 221 userconf_pdevnam(devno); 222 printf(" at"); 223 cfp = cd->cf_pspec; 224 if (cfp == NULL) 225 printf(" root"); 226 else if (cfp->cfp_parent != NULL && cfp->cfp_unit != -1) 227 printf(" %s%d", cfp->cfp_parent, cfp->cfp_unit); 228 else 229 printf(" %s?", cfp->cfp_parent != NULL ? cfp->cfp_parent 230 : cfp->cfp_iattr); 231 switch (cd->cf_fstate) { 232 case FSTATE_NOTFOUND: 233 case FSTATE_FOUND: 234 case FSTATE_STAR: 235 break; 236 case FSTATE_DNOTFOUND: 237 case FSTATE_DSTAR: 238 printf(" disable"); 239 break; 240 default: 241 printf(" ???"); 242 break; 243 } 244 if (cfp) { 245 l = cd->cf_loc; 246 ia = cfiattr_lookup(cfp->cfp_iattr, 0); 247 KASSERT(ia); 248 ld = ia->ci_locdesc; 249 nld = ia->ci_loclen; 250 for (i = 0; i < nld; i++) { 251 printf(" %s ", ld[i].cld_name); 252 if (!ld[i].cld_defaultstr 253 || (l[i] != ld[i].cld_default)) 254 userconf_pnum(l[i]); 255 else 256 printf("?"); 257 } 258 } 259 printf("\n"); 260 } 261 262 static int 263 userconf_number(char *c, int *val) 264 { 265 u_int num = 0; 266 int neg = 0; 267 int base = 10; 268 269 if (*c == '-') { 270 neg = 1; 271 c++; 272 } 273 if (*c == '0') { 274 base = 8; 275 c++; 276 if (*c == 'x' || *c == 'X') { 277 base = 16; 278 c++; 279 } 280 } 281 while (*c != '\n' && *c != '\t' && *c != ' ' && *c != '\0') { 282 u_char cc = *c; 283 284 if (cc >= '0' && cc <= '9') 285 cc = cc - '0'; 286 else if (cc >= 'a' && cc <= 'f') 287 cc = cc - 'a' + 10; 288 else if (cc >= 'A' && cc <= 'F') 289 cc = cc - 'A' + 10; 290 else 291 return (-1); 292 293 if (cc > base) 294 return (-1); 295 num = num * base + cc; 296 c++; 297 } 298 299 if (neg && num > INT_MAX) /* overflow */ 300 return (1); 301 *val = neg ? - num : num; 302 return (0); 303 } 304 305 static int 306 userconf_device(char *cmd, int *len, short *unit, short *state) 307 { 308 short u = 0, s = FSTATE_FOUND; 309 int l = 0; 310 char *c; 311 312 c = cmd; 313 while (*c >= 'a' && *c <= 'z') { 314 l++; 315 c++; 316 } 317 if (*c == '*') { 318 s = FSTATE_STAR; 319 c++; 320 } else { 321 while (*c >= '0' && *c <= '9') { 322 s = FSTATE_NOTFOUND; 323 u = u*10 + *c - '0'; 324 c++; 325 } 326 } 327 while (*c == ' ' || *c == '\t' || *c == '\n') 328 c++; 329 330 if (*c == '\0') { 331 *len = l; 332 *unit = u; 333 *state = s; 334 return(0); 335 } 336 337 return(-1); 338 } 339 340 static void 341 userconf_modify(const struct cflocdesc *item, int *val) 342 { 343 int ok = 0; 344 int a; 345 char *c; 346 347 while (!ok) { 348 printf("%s [", item->cld_name); 349 if (item->cld_defaultstr && (*val == item->cld_default)) 350 printf("?"); 351 else 352 userconf_pnum(*val); 353 printf("] ? "); 354 355 getsn(userconf_argbuf, sizeof(userconf_argbuf)); 356 357 c = userconf_argbuf; 358 while (*c == ' ' || *c == '\t' || *c == '\n') c++; 359 360 if (*c != '\0') { 361 if (*c == '?') { 362 if (item->cld_defaultstr) { 363 *val = item->cld_default; 364 ok = 1; 365 } else 366 printf("No default\n"); 367 } else if (userconf_number(c, &a) == 0) { 368 *val = a; 369 ok = 1; 370 } else { 371 printf("Unknown argument\n"); 372 } 373 } else { 374 ok = 1; 375 } 376 } 377 } 378 379 static void 380 userconf_change(int devno) 381 { 382 struct cfdata *cd; 383 char c = '\0'; 384 int *l; 385 int ln; 386 const struct cfiattrdata *ia; 387 const struct cflocdesc *ld; 388 int nld; 389 390 if (devno <= userconf_maxdev) { 391 392 userconf_pdev(devno); 393 394 while (c != 'y' && c != 'Y' && c != 'n' && c != 'N') { 395 printf("change (y/n) ?"); 396 c = cngetc(); 397 printf("\n"); 398 } 399 400 if (c == 'y' || c == 'Y') { 401 402 /* XXX add cmd 'c' <devno> */ 403 userconf_hist_cmd('c'); 404 userconf_hist_int(devno); 405 406 cd = &cfdata[devno]; 407 l = cd->cf_loc; 408 ia = cfiattr_lookup(cd->cf_pspec->cfp_iattr, 0); 409 KASSERT(ia); 410 ld = ia->ci_locdesc; 411 nld = ia->ci_loclen; 412 413 for (ln = 0; ln < nld; ln++) 414 { 415 userconf_modify(&ld[ln], l); 416 417 /* XXX add *l */ 418 userconf_hist_int(*l); 419 420 l++; 421 } 422 423 printf("[%3d] ", devno); 424 userconf_pdevnam(devno); 425 printf(" changed\n"); 426 userconf_pdev(devno); 427 428 /* XXX add eoc */ 429 userconf_hist_eoc(); 430 431 } 432 } else { 433 printf("Unknown devno (max is %d)\n", userconf_maxdev); 434 } 435 } 436 437 static void 438 userconf_disable(int devno) 439 { 440 int done = 0; 441 442 if (devno <= userconf_maxdev) { 443 switch (cfdata[devno].cf_fstate) { 444 case FSTATE_NOTFOUND: 445 cfdata[devno].cf_fstate = FSTATE_DNOTFOUND; 446 break; 447 case FSTATE_STAR: 448 cfdata[devno].cf_fstate = FSTATE_DSTAR; 449 break; 450 case FSTATE_DNOTFOUND: 451 case FSTATE_DSTAR: 452 done = 1; 453 break; 454 default: 455 printf("Error unknown state\n"); 456 break; 457 } 458 459 printf("[%3d] ", devno); 460 userconf_pdevnam(devno); 461 if (done) { 462 printf(" already"); 463 } else { 464 /* XXX add cmd 'd' <devno> eoc */ 465 userconf_hist_cmd('d'); 466 userconf_hist_int(devno); 467 userconf_hist_eoc(); 468 } 469 printf(" disabled\n"); 470 } else { 471 printf("Unknown devno (max is %d)\n", userconf_maxdev); 472 } 473 } 474 475 static void 476 userconf_enable(int devno) 477 { 478 int done = 0; 479 480 if (devno <= userconf_maxdev) { 481 switch (cfdata[devno].cf_fstate) { 482 case FSTATE_DNOTFOUND: 483 cfdata[devno].cf_fstate = FSTATE_NOTFOUND; 484 break; 485 case FSTATE_DSTAR: 486 cfdata[devno].cf_fstate = FSTATE_STAR; 487 break; 488 case FSTATE_NOTFOUND: 489 case FSTATE_STAR: 490 done = 1; 491 break; 492 default: 493 printf("Error unknown state\n"); 494 break; 495 } 496 497 printf("[%3d] ", devno); 498 userconf_pdevnam(devno); 499 if (done) { 500 printf(" already"); 501 } else { 502 /* XXX add cmd 'e' <devno> eoc */ 503 userconf_hist_cmd('d'); 504 userconf_hist_int(devno); 505 userconf_hist_eoc(); 506 } 507 printf(" enabled\n"); 508 } else { 509 printf("Unknown devno (max is %d)\n", userconf_maxdev); 510 } 511 } 512 513 static void 514 userconf_help(void) 515 { 516 int j = 0, k; 517 518 printf("command args description\n"); 519 while (*userconf_cmds[j] != '\0') { 520 printf(userconf_cmds[j]); 521 k = strlen(userconf_cmds[j]); 522 while (k < 10) { 523 printf(" "); 524 k++; 525 } 526 switch (*userconf_cmds[j+1]) { 527 case 'L': 528 printf("[count] number of lines before more"); 529 break; 530 case 'b': 531 printf("8|10|16 base on large numbers"); 532 break; 533 case 'c': 534 printf("devno|dev change devices"); 535 break; 536 case 'd': 537 printf("devno|dev disable devices"); 538 break; 539 case 'e': 540 printf("devno|dev enable devices"); 541 break; 542 case 'f': 543 printf("devno|dev find devices"); 544 break; 545 case 'h': 546 printf(" this message"); 547 break; 548 case 'l': 549 printf(" list configuration"); 550 break; 551 case 'q': 552 printf(" leave userconf"); 553 break; 554 default: 555 printf(" don't know"); 556 break; 557 } 558 printf("\n"); 559 j += 2; 560 } 561 } 562 563 static void 564 userconf_list(void) 565 { 566 int i = 0; 567 568 userconf_cnt = 0; 569 570 while (cfdata[i].cf_name != NULL) { 571 if (userconf_more()) 572 break; 573 userconf_pdev(i++); 574 } 575 576 userconf_cnt = -1; 577 } 578 579 static void 580 userconf_common_dev(char *dev, int len, short unit, short state, char routine) 581 { 582 int i = 0; 583 584 switch (routine) { 585 case UC_CHANGE: 586 break; 587 default: 588 userconf_cnt = 0; 589 break; 590 } 591 592 while (cfdata[i].cf_name != NULL) { 593 if (strlen(cfdata[i].cf_name) == len) { 594 595 /* 596 * Ok, if device name is correct 597 * If state == FSTATE_FOUND, look for "dev" 598 * If state == FSTATE_STAR, look for "dev*" 599 * If state == FSTATE_NOTFOUND, look for "dev0" 600 */ 601 if (strncasecmp(dev, cfdata[i].cf_name, 602 len) == 0 && 603 (state == FSTATE_FOUND || 604 (state == FSTATE_STAR && 605 (cfdata[i].cf_fstate == FSTATE_STAR || 606 cfdata[i].cf_fstate == FSTATE_DSTAR)) || 607 (state == FSTATE_NOTFOUND && 608 cfdata[i].cf_unit == unit && 609 (cfdata[i].cf_fstate == FSTATE_NOTFOUND || 610 cfdata[i].cf_fstate == FSTATE_DNOTFOUND)))) { 611 if (userconf_more()) 612 break; 613 switch (routine) { 614 case UC_CHANGE: 615 userconf_change(i); 616 break; 617 case UC_ENABLE: 618 userconf_enable(i); 619 break; 620 case UC_DISABLE: 621 userconf_disable(i); 622 break; 623 case UC_FIND: 624 userconf_pdev(i); 625 break; 626 default: 627 printf("Unknown routine /%c/\n", 628 routine); 629 break; 630 } 631 } 632 } 633 i++; 634 } 635 636 switch (routine) { 637 case UC_CHANGE: 638 break; 639 default: 640 userconf_cnt = -1; 641 break; 642 } 643 } 644 645 #if 0 646 static void 647 userconf_add_read(char *prompt, char field, char *dev, int len, int *val) 648 { 649 int ok = 0; 650 int a; 651 char *c; 652 653 *val = -1; 654 655 while (!ok) { 656 printf("%s ? ", prompt); 657 658 getsn(userconf_argbuf, sizeof(userconf_argbuf)); 659 660 c = userconf_argbuf; 661 while (*c == ' ' || *c == '\t' || *c == '\n') c++; 662 663 if (*c != '\0') { 664 if (userconf_number(c, &a) == 0) { 665 if (a > userconf_maxdev) { 666 printf("Unknown devno (max is %d)\n", 667 userconf_maxdev); 668 } else if (strncasecmp(dev, 669 cfdata[a].cf_name, len) != 0 && 670 field == 'a') { 671 printf("Not same device type\n"); 672 } else { 673 *val = a; 674 ok = 1; 675 } 676 } else if (*c == '?') { 677 userconf_common_dev(dev, len, 0, 678 FSTATE_FOUND, UC_FIND); 679 } else if (*c == 'q' || *c == 'Q') { 680 ok = 1; 681 } else { 682 printf("Unknown argument\n"); 683 } 684 } else { 685 ok = 1; 686 } 687 } 688 } 689 #endif /* 0 */ 690 691 static int 692 userconf_parse(char *cmd) 693 { 694 char *c, *v; 695 int i = 0, j = 0, k, a; 696 short unit, state; 697 698 c = cmd; 699 while (*c == ' ' || *c == '\t') 700 c++; 701 v = c; 702 while (*c != ' ' && *c != '\t' && *c != '\n' && *c != '\0') { 703 c++; 704 i++; 705 } 706 707 k = -1; 708 while (*userconf_cmds[j] != '\0') { 709 if (strlen(userconf_cmds[j]) == i) { 710 if (strncasecmp(v, userconf_cmds[j], i) == 0) 711 k = j; 712 } 713 j += 2; 714 } 715 716 while (*c == ' ' || *c == '\t' || *c == '\n') 717 c++; 718 719 if (k == -1) { 720 if (*v != '\n') 721 printf("Unknown command, try help\n"); 722 } else { 723 switch (*userconf_cmds[k+1]) { 724 case 'L': 725 if (*c == '\0') 726 printf("Argument expected\n"); 727 else if (userconf_number(c, &a) == 0) 728 userconf_lines = a; 729 else 730 printf("Unknown argument\n"); 731 break; 732 case 'b': 733 if (*c == '\0') 734 printf("8|10|16 expected\n"); 735 else if (userconf_number(c, &a) == 0) { 736 if (a == 8 || a == 10 || a == 16) { 737 userconf_base = a; 738 } else { 739 printf("8|10|16 expected\n"); 740 } 741 } else 742 printf("Unknown argument\n"); 743 break; 744 case 'c': 745 if (*c == '\0') 746 printf("DevNo or Dev expected\n"); 747 else if (userconf_number(c, &a) == 0) 748 userconf_change(a); 749 else if (userconf_device(c, &a, &unit, &state) == 0) 750 userconf_common_dev(c, a, unit, state, UC_CHANGE); 751 else 752 printf("Unknown argument\n"); 753 break; 754 case 'd': 755 if (*c == '\0') 756 printf("Attr, DevNo or Dev expected\n"); 757 else if (userconf_number(c, &a) == 0) 758 userconf_disable(a); 759 else if (userconf_device(c, &a, &unit, &state) == 0) 760 userconf_common_dev(c, a, unit, state, UC_DISABLE); 761 else 762 printf("Unknown argument\n"); 763 break; 764 case 'e': 765 if (*c == '\0') 766 printf("Attr, DevNo or Dev expected\n"); 767 else if (userconf_number(c, &a) == 0) 768 userconf_enable(a); 769 else if (userconf_device(c, &a, &unit, &state) == 0) 770 userconf_common_dev(c, a, unit, state, UC_ENABLE); 771 else 772 printf("Unknown argument\n"); 773 break; 774 case 'f': 775 if (*c == '\0') 776 printf("DevNo or Dev expected\n"); 777 else if (userconf_number(c, &a) == 0) 778 userconf_pdev(a); 779 else if (userconf_device(c, &a, &unit, &state) == 0) 780 userconf_common_dev(c, a, unit, state, UC_FIND); 781 else 782 printf("Unknown argument\n"); 783 break; 784 case 'h': 785 userconf_help(); 786 break; 787 case 'l': 788 if (*c == '\0') 789 userconf_list(); 790 else 791 printf("Unknown argument\n"); 792 break; 793 case 'q': 794 /* XXX add cmd 'q' eoc */ 795 userconf_hist_cmd('q'); 796 userconf_hist_eoc(); 797 return(-1); 798 case 's': 799 default: 800 printf("Unknown command\n"); 801 break; 802 } 803 } 804 return(0); 805 } 806 807 extern void user_config(void); 808 809 void 810 user_config(void) 811 { 812 char prompt[] = "uc> "; 813 814 userconf_init(); 815 printf("userconf: configure system autoconfiguration:\n"); 816 817 while (1) { 818 printf(prompt); 819 if (getsn(userconf_cmdbuf, sizeof(userconf_cmdbuf)) > 0 && 820 userconf_parse(userconf_cmdbuf)) 821 break; 822 } 823 printf("Continuing...\n"); 824 } 825 826 /* 827 * XXX shouldn't this be a common function? 828 */ 829 static int 830 getsn(char *cp, int size) 831 { 832 char *lp; 833 int c, len; 834 835 cnpollc(1); 836 837 lp = cp; 838 len = 0; 839 for (;;) { 840 c = cngetc(); 841 switch (c) { 842 case '\n': 843 case '\r': 844 printf("\n"); 845 *lp++ = '\0'; 846 cnpollc(0); 847 return (len); 848 case '\b': 849 case '\177': 850 case '#': 851 if (len) { 852 --len; 853 --lp; 854 printf("\b \b"); 855 } 856 continue; 857 case '@': 858 case 'u'&037: 859 len = 0; 860 lp = cp; 861 printf("\n"); 862 continue; 863 default: 864 if (len + 1 >= size || c < ' ') { 865 printf("\007"); 866 continue; 867 } 868 printf("%c", c); 869 ++len; 870 *lp++ = c; 871 } 872 } 873 } 874