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