1 /* $OpenBSD: vfscanf.c,v 1.25 2009/11/09 00:18:27 kurt Exp $ */ 2 /*- 3 * Copyright (c) 1990, 1993 4 * The Regents of the University of California. All rights reserved. 5 * 6 * This code is derived from software contributed to Berkeley by 7 * Chris Torek. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34 #include <ctype.h> 35 #include <inttypes.h> 36 #include <stdarg.h> 37 #include <stddef.h> 38 #include <stdio.h> 39 #include <stdlib.h> 40 #include "local.h" 41 42 #ifdef FLOATING_POINT 43 #include "floatio.h" 44 #endif 45 46 #define BUF 513 /* Maximum length of numeric string. */ 47 48 /* 49 * Flags used during conversion. 50 */ 51 #define LONG 0x00001 /* l: long or double */ 52 #define LONGDBL 0x00002 /* L: long double; unimplemented */ 53 #define SHORT 0x00004 /* h: short */ 54 #define SHORTSHORT 0x00008 /* hh: 8 bit integer */ 55 #define LLONG 0x00010 /* ll: long long (+ deprecated q: quad) */ 56 #define POINTER 0x00020 /* p: void * (as hex) */ 57 #define SIZEINT 0x00040 /* z: (signed) size_t */ 58 #define MAXINT 0x00080 /* j: intmax_t */ 59 #define PTRINT 0x00100 /* t: ptrdiff_t */ 60 #define NOSKIP 0x00200 /* [ or c: do not skip blanks */ 61 #define SUPPRESS 0x00400 /* *: suppress assignment */ 62 #define UNSIGNED 0x00800 /* %[oupxX] conversions */ 63 64 /* 65 * The following are used in numeric conversions only: 66 * SIGNOK, HAVESIGN, NDIGITS, DPTOK, and EXPOK are for floating point; 67 * SIGNOK, HAVESIGN, NDIGITS, PFXOK, and NZDIGITS are for integral. 68 */ 69 #define SIGNOK 0x01000 /* +/- is (still) legal */ 70 #define HAVESIGN 0x02000 /* sign detected */ 71 #define NDIGITS 0x04000 /* no digits detected */ 72 73 #define DPTOK 0x08000 /* (float) decimal point is still legal */ 74 #define EXPOK 0x10000 /* (float) exponent (e+3, etc) still legal */ 75 76 #define PFXOK 0x08000 /* 0x prefix is (still) legal */ 77 #define NZDIGITS 0x10000 /* no zero digits detected */ 78 79 /* 80 * Conversion types. 81 */ 82 #define CT_CHAR 0 /* %c conversion */ 83 #define CT_CCL 1 /* %[...] conversion */ 84 #define CT_STRING 2 /* %s conversion */ 85 #define CT_INT 3 /* integer, i.e., strtoimax or strtoumax */ 86 #define CT_FLOAT 4 /* floating, i.e., strtod */ 87 88 #define u_char unsigned char 89 #define u_long unsigned long 90 91 static u_char *__sccl(char *, u_char *); 92 93 #if !defined(VFSCANF) 94 #define VFSCANF vfscanf 95 #endif 96 97 /* 98 * vfscanf 99 */ 100 int 101 VFSCANF(FILE *fp, const char *fmt0, __va_list ap) 102 { 103 u_char *fmt = (u_char *)fmt0; 104 int c; /* character from format, or conversion */ 105 size_t width; /* field width, or 0 */ 106 char *p; /* points into all kinds of strings */ 107 int n; /* handy integer */ 108 int flags; /* flags as defined above */ 109 char *p0; /* saves original value of p when necessary */ 110 int nassigned; /* number of fields assigned */ 111 int nread; /* number of characters consumed from fp */ 112 int base; /* base argument to strtoimax/strtouimax */ 113 char ccltab[256]; /* character class table for %[...] */ 114 char buf[BUF]; /* buffer for numeric conversions */ 115 116 /* `basefix' is used to avoid `if' tests in the integer scanner */ 117 static short basefix[17] = 118 { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; 119 120 FLOCKFILE(fp); 121 _SET_ORIENTATION(fp, -1); 122 123 nassigned = 0; 124 nread = 0; 125 base = 0; /* XXX just to keep gcc happy */ 126 for (;;) { 127 c = *fmt++; 128 if (c == 0) { 129 FUNLOCKFILE(fp); 130 return (nassigned); 131 } 132 if (isspace(c)) { 133 while ((fp->_r > 0 || __srefill(fp) == 0) && 134 isspace(*fp->_p)) 135 nread++, fp->_r--, fp->_p++; 136 continue; 137 } 138 if (c != '%') 139 goto literal; 140 width = 0; 141 flags = 0; 142 /* 143 * switch on the format. continue if done; 144 * break once format type is derived. 145 */ 146 again: c = *fmt++; 147 switch (c) { 148 case '%': 149 literal: 150 if (fp->_r <= 0 && __srefill(fp)) 151 goto input_failure; 152 if (*fp->_p != c) 153 goto match_failure; 154 fp->_r--, fp->_p++; 155 nread++; 156 continue; 157 158 case '*': 159 flags |= SUPPRESS; 160 goto again; 161 case 'j': 162 flags |= MAXINT; 163 goto again; 164 case 'L': 165 flags |= LONGDBL; 166 goto again; 167 case 'h': 168 if (*fmt == 'h') { 169 fmt++; 170 flags |= SHORTSHORT; 171 } else { 172 flags |= SHORT; 173 } 174 goto again; 175 case 'l': 176 if (*fmt == 'l') { 177 fmt++; 178 flags |= LLONG; 179 } else { 180 flags |= LONG; 181 } 182 goto again; 183 case 'q': 184 flags |= LLONG; /* deprecated */ 185 goto again; 186 case 't': 187 flags |= PTRINT; 188 goto again; 189 case 'z': 190 flags |= SIZEINT; 191 goto again; 192 193 case '0': case '1': case '2': case '3': case '4': 194 case '5': case '6': case '7': case '8': case '9': 195 width = width * 10 + c - '0'; 196 goto again; 197 198 /* 199 * Conversions. 200 * Those marked `compat' are for 4.[123]BSD compatibility. 201 * 202 * (According to ANSI, E and X formats are supposed 203 * to the same as e and x. Sorry about that.) 204 */ 205 case 'D': /* compat */ 206 flags |= LONG; 207 /* FALLTHROUGH */ 208 case 'd': 209 c = CT_INT; 210 base = 10; 211 break; 212 213 case 'i': 214 c = CT_INT; 215 base = 0; 216 break; 217 218 case 'O': /* compat */ 219 flags |= LONG; 220 /* FALLTHROUGH */ 221 case 'o': 222 c = CT_INT; 223 flags |= UNSIGNED; 224 base = 8; 225 break; 226 227 case 'u': 228 c = CT_INT; 229 flags |= UNSIGNED; 230 base = 10; 231 break; 232 233 case 'X': 234 case 'x': 235 flags |= PFXOK; /* enable 0x prefixing */ 236 c = CT_INT; 237 flags |= UNSIGNED; 238 base = 16; 239 break; 240 241 #ifdef FLOATING_POINT 242 case 'E': 243 case 'G': 244 case 'e': 245 case 'f': 246 case 'F': 247 case 'g': 248 c = CT_FLOAT; 249 break; 250 #endif 251 252 case 's': 253 c = CT_STRING; 254 break; 255 256 case '[': 257 fmt = __sccl(ccltab, fmt); 258 flags |= NOSKIP; 259 c = CT_CCL; 260 break; 261 262 case 'c': 263 flags |= NOSKIP; 264 c = CT_CHAR; 265 break; 266 267 case 'p': /* pointer format is like hex */ 268 flags |= POINTER | PFXOK; 269 c = CT_INT; 270 flags |= UNSIGNED; 271 base = 16; 272 break; 273 274 case 'n': 275 if (flags & SUPPRESS) 276 continue; 277 if (flags & SHORTSHORT) 278 *va_arg(ap, __signed char *) = nread; 279 else if (flags & SHORT) 280 *va_arg(ap, short *) = nread; 281 else if (flags & LONG) 282 *va_arg(ap, long *) = nread; 283 else if (flags & SIZEINT) 284 *va_arg(ap, ssize_t *) = nread; 285 else if (flags & PTRINT) 286 *va_arg(ap, ptrdiff_t *) = nread; 287 else if (flags & LLONG) 288 *va_arg(ap, long long *) = nread; 289 else if (flags & MAXINT) 290 *va_arg(ap, intmax_t *) = nread; 291 else 292 *va_arg(ap, int *) = nread; 293 continue; 294 295 /* 296 * Disgusting backwards compatibility hacks. XXX 297 */ 298 case '\0': /* compat */ 299 FUNLOCKFILE(fp); 300 return (EOF); 301 302 default: /* compat */ 303 if (isupper(c)) 304 flags |= LONG; 305 c = CT_INT; 306 base = 10; 307 break; 308 } 309 310 /* 311 * We have a conversion that requires input. 312 */ 313 if (fp->_r <= 0 && __srefill(fp)) 314 goto input_failure; 315 316 /* 317 * Consume leading white space, except for formats 318 * that suppress this. 319 */ 320 if ((flags & NOSKIP) == 0) { 321 while (isspace(*fp->_p)) { 322 nread++; 323 if (--fp->_r > 0) 324 fp->_p++; 325 else if (__srefill(fp)) 326 goto input_failure; 327 } 328 /* 329 * Note that there is at least one character in 330 * the buffer, so conversions that do not set NOSKIP 331 * ca no longer result in an input failure. 332 */ 333 } 334 335 /* 336 * Do the conversion. 337 */ 338 switch (c) { 339 340 case CT_CHAR: 341 /* scan arbitrary characters (sets NOSKIP) */ 342 if (width == 0) 343 width = 1; 344 if (flags & SUPPRESS) { 345 size_t sum = 0; 346 for (;;) { 347 if ((n = fp->_r) < width) { 348 sum += n; 349 width -= n; 350 fp->_p += n; 351 if (__srefill(fp)) { 352 if (sum == 0) 353 goto input_failure; 354 break; 355 } 356 } else { 357 sum += width; 358 fp->_r -= width; 359 fp->_p += width; 360 break; 361 } 362 } 363 nread += sum; 364 } else { 365 size_t r = fread((void *)va_arg(ap, char *), 1, 366 width, fp); 367 368 if (r == 0) 369 goto input_failure; 370 nread += r; 371 nassigned++; 372 } 373 break; 374 375 case CT_CCL: 376 /* scan a (nonempty) character class (sets NOSKIP) */ 377 if (width == 0) 378 width = (size_t)~0; /* `infinity' */ 379 /* take only those things in the class */ 380 if (flags & SUPPRESS) { 381 n = 0; 382 while (ccltab[*fp->_p]) { 383 n++, fp->_r--, fp->_p++; 384 if (--width == 0) 385 break; 386 if (fp->_r <= 0 && __srefill(fp)) { 387 if (n == 0) 388 goto input_failure; 389 break; 390 } 391 } 392 if (n == 0) 393 goto match_failure; 394 } else { 395 p0 = p = va_arg(ap, char *); 396 while (ccltab[*fp->_p]) { 397 fp->_r--; 398 *p++ = *fp->_p++; 399 if (--width == 0) 400 break; 401 if (fp->_r <= 0 && __srefill(fp)) { 402 if (p == p0) 403 goto input_failure; 404 break; 405 } 406 } 407 n = p - p0; 408 if (n == 0) 409 goto match_failure; 410 *p = '\0'; 411 nassigned++; 412 } 413 nread += n; 414 break; 415 416 case CT_STRING: 417 /* like CCL, but zero-length string OK, & no NOSKIP */ 418 if (width == 0) 419 width = (size_t)~0; 420 if (flags & SUPPRESS) { 421 n = 0; 422 while (!isspace(*fp->_p)) { 423 n++, fp->_r--, fp->_p++; 424 if (--width == 0) 425 break; 426 if (fp->_r <= 0 && __srefill(fp)) 427 break; 428 } 429 nread += n; 430 } else { 431 p0 = p = va_arg(ap, char *); 432 while (!isspace(*fp->_p)) { 433 fp->_r--; 434 *p++ = *fp->_p++; 435 if (--width == 0) 436 break; 437 if (fp->_r <= 0 && __srefill(fp)) 438 break; 439 } 440 *p = '\0'; 441 nread += p - p0; 442 nassigned++; 443 } 444 continue; 445 446 case CT_INT: 447 /* scan an integer as if by strtoimax/strtoumax */ 448 #ifdef hardway 449 if (width == 0 || width > sizeof(buf) - 1) 450 width = sizeof(buf) - 1; 451 #else 452 /* size_t is unsigned, hence this optimisation */ 453 if (--width > sizeof(buf) - 2) 454 width = sizeof(buf) - 2; 455 width++; 456 #endif 457 flags |= SIGNOK | NDIGITS | NZDIGITS; 458 for (p = buf; width; width--) { 459 c = *fp->_p; 460 /* 461 * Switch on the character; `goto ok' 462 * if we accept it as a part of number. 463 */ 464 switch (c) { 465 466 /* 467 * The digit 0 is always legal, but is 468 * special. For %i conversions, if no 469 * digits (zero or nonzero) have been 470 * scanned (only signs), we will have 471 * base==0. In that case, we should set 472 * it to 8 and enable 0x prefixing. 473 * Also, if we have not scanned zero digits 474 * before this, do not turn off prefixing 475 * (someone else will turn it off if we 476 * have scanned any nonzero digits). 477 */ 478 case '0': 479 if (base == 0) { 480 base = 8; 481 flags |= PFXOK; 482 } 483 if (flags & NZDIGITS) 484 flags &= ~(SIGNOK|NZDIGITS|NDIGITS); 485 else 486 flags &= ~(SIGNOK|PFXOK|NDIGITS); 487 goto ok; 488 489 /* 1 through 7 always legal */ 490 case '1': case '2': case '3': 491 case '4': case '5': case '6': case '7': 492 base = basefix[base]; 493 flags &= ~(SIGNOK | PFXOK | NDIGITS); 494 goto ok; 495 496 /* digits 8 and 9 ok iff decimal or hex */ 497 case '8': case '9': 498 base = basefix[base]; 499 if (base <= 8) 500 break; /* not legal here */ 501 flags &= ~(SIGNOK | PFXOK | NDIGITS); 502 goto ok; 503 504 /* letters ok iff hex */ 505 case 'A': case 'B': case 'C': 506 case 'D': case 'E': case 'F': 507 case 'a': case 'b': case 'c': 508 case 'd': case 'e': case 'f': 509 /* no need to fix base here */ 510 if (base <= 10) 511 break; /* not legal here */ 512 flags &= ~(SIGNOK | PFXOK | NDIGITS); 513 goto ok; 514 515 /* sign ok only as first character */ 516 case '+': case '-': 517 if (flags & SIGNOK) { 518 flags &= ~SIGNOK; 519 flags |= HAVESIGN; 520 goto ok; 521 } 522 break; 523 524 /* 525 * x ok iff flag still set and 2nd char (or 526 * 3rd char if we have a sign). 527 */ 528 case 'x': case 'X': 529 if ((flags & PFXOK) && p == 530 buf + 1 + !!(flags & HAVESIGN)) { 531 base = 16; /* if %i */ 532 flags &= ~PFXOK; 533 goto ok; 534 } 535 break; 536 } 537 538 /* 539 * If we got here, c is not a legal character 540 * for a number. Stop accumulating digits. 541 */ 542 break; 543 ok: 544 /* 545 * c is legal: store it and look at the next. 546 */ 547 *p++ = c; 548 if (--fp->_r > 0) 549 fp->_p++; 550 else if (__srefill(fp)) 551 break; /* EOF */ 552 } 553 /* 554 * If we had only a sign, it is no good; push 555 * back the sign. If the number ends in `x', 556 * it was [sign] '0' 'x', so push back the x 557 * and treat it as [sign] '0'. 558 */ 559 if (flags & NDIGITS) { 560 if (p > buf) 561 (void) ungetc(*(u_char *)--p, fp); 562 goto match_failure; 563 } 564 c = ((u_char *)p)[-1]; 565 if (c == 'x' || c == 'X') { 566 --p; 567 (void) ungetc(c, fp); 568 } 569 if ((flags & SUPPRESS) == 0) { 570 uintmax_t res; 571 572 *p = '\0'; 573 if (flags & UNSIGNED) 574 res = strtoumax(buf, NULL, base); 575 else 576 res = strtoimax(buf, NULL, base); 577 if (flags & POINTER) 578 *va_arg(ap, void **) = 579 (void *)(uintptr_t)res; 580 else if (flags & MAXINT) 581 *va_arg(ap, intmax_t *) = res; 582 else if (flags & LLONG) 583 *va_arg(ap, long long *) = res; 584 else if (flags & SIZEINT) 585 *va_arg(ap, ssize_t *) = res; 586 else if (flags & PTRINT) 587 *va_arg(ap, ptrdiff_t *) = res; 588 else if (flags & LONG) 589 *va_arg(ap, long *) = res; 590 else if (flags & SHORT) 591 *va_arg(ap, short *) = res; 592 else if (flags & SHORTSHORT) 593 *va_arg(ap, __signed char *) = res; 594 else 595 *va_arg(ap, int *) = res; 596 nassigned++; 597 } 598 nread += p - buf; 599 break; 600 601 #ifdef FLOATING_POINT 602 case CT_FLOAT: 603 /* scan a floating point number as if by strtod */ 604 #ifdef hardway 605 if (width == 0 || width > sizeof(buf) - 1) 606 width = sizeof(buf) - 1; 607 #else 608 /* size_t is unsigned, hence this optimisation */ 609 if (--width > sizeof(buf) - 2) 610 width = sizeof(buf) - 2; 611 width++; 612 #endif 613 flags |= SIGNOK | NDIGITS | DPTOK | EXPOK; 614 for (p = buf; width; width--) { 615 c = *fp->_p; 616 /* 617 * This code mimicks the integer conversion 618 * code, but is much simpler. 619 */ 620 switch (c) { 621 622 case '0': case '1': case '2': case '3': 623 case '4': case '5': case '6': case '7': 624 case '8': case '9': 625 flags &= ~(SIGNOK | NDIGITS); 626 goto fok; 627 628 case '+': case '-': 629 if (flags & SIGNOK) { 630 flags &= ~SIGNOK; 631 goto fok; 632 } 633 break; 634 case '.': 635 if (flags & DPTOK) { 636 flags &= ~(SIGNOK | DPTOK); 637 goto fok; 638 } 639 break; 640 case 'e': case 'E': 641 /* no exponent without some digits */ 642 if ((flags&(NDIGITS|EXPOK)) == EXPOK) { 643 flags = 644 (flags & ~(EXPOK|DPTOK)) | 645 SIGNOK | NDIGITS; 646 goto fok; 647 } 648 break; 649 } 650 break; 651 fok: 652 *p++ = c; 653 if (--fp->_r > 0) 654 fp->_p++; 655 else if (__srefill(fp)) 656 break; /* EOF */ 657 } 658 /* 659 * If no digits, might be missing exponent digits 660 * (just give back the exponent) or might be missing 661 * regular digits, but had sign and/or decimal point. 662 */ 663 if (flags & NDIGITS) { 664 if (flags & EXPOK) { 665 /* no digits at all */ 666 while (p > buf) 667 ungetc(*(u_char *)--p, fp); 668 goto match_failure; 669 } 670 /* just a bad exponent (e and maybe sign) */ 671 c = *(u_char *)--p; 672 if (c != 'e' && c != 'E') { 673 (void) ungetc(c, fp);/* sign */ 674 c = *(u_char *)--p; 675 } 676 (void) ungetc(c, fp); 677 } 678 if ((flags & SUPPRESS) == 0) { 679 double res; 680 681 *p = '\0'; 682 res = strtod(buf, (char **) NULL); 683 if (flags & LONGDBL) 684 *va_arg(ap, long double *) = res; 685 else if (flags & LONG) 686 *va_arg(ap, double *) = res; 687 else 688 *va_arg(ap, float *) = res; 689 nassigned++; 690 } 691 nread += p - buf; 692 break; 693 #endif /* FLOATING_POINT */ 694 } 695 } 696 input_failure: 697 if (nassigned == 0) 698 nassigned = -1; 699 match_failure: 700 FUNLOCKFILE(fp); 701 return (nassigned); 702 } 703 704 /* 705 * Fill in the given table from the scanset at the given format 706 * (just after `['). Return a pointer to the character past the 707 * closing `]'. The table has a 1 wherever characters should be 708 * considered part of the scanset. 709 */ 710 static u_char * 711 __sccl(char *tab, u_char *fmt) 712 { 713 int c, n, v; 714 715 /* first `clear' the whole table */ 716 c = *fmt++; /* first char hat => negated scanset */ 717 if (c == '^') { 718 v = 1; /* default => accept */ 719 c = *fmt++; /* get new first char */ 720 } else 721 v = 0; /* default => reject */ 722 /* should probably use memset here */ 723 for (n = 0; n < 256; n++) 724 tab[n] = v; 725 if (c == 0) 726 return (fmt - 1);/* format ended before closing ] */ 727 728 /* 729 * Now set the entries corresponding to the actual scanset 730 * to the opposite of the above. 731 * 732 * The first character may be ']' (or '-') without being special; 733 * the last character may be '-'. 734 */ 735 v = 1 - v; 736 for (;;) { 737 tab[c] = v; /* take character c */ 738 doswitch: 739 n = *fmt++; /* and examine the next */ 740 switch (n) { 741 742 case 0: /* format ended too soon */ 743 return (fmt - 1); 744 745 case '-': 746 /* 747 * A scanset of the form 748 * [01+-] 749 * is defined as `the digit 0, the digit 1, 750 * the character +, the character -', but 751 * the effect of a scanset such as 752 * [a-zA-Z0-9] 753 * is implementation defined. The V7 Unix 754 * scanf treats `a-z' as `the letters a through 755 * z', but treats `a-a' as `the letter a, the 756 * character -, and the letter a'. 757 * 758 * For compatibility, the `-' is not considerd 759 * to define a range if the character following 760 * it is either a close bracket (required by ANSI) 761 * or is not numerically greater than the character 762 * we just stored in the table (c). 763 */ 764 n = *fmt; 765 if (n == ']' || n < c) { 766 c = '-'; 767 break; /* resume the for(;;) */ 768 } 769 fmt++; 770 do { /* fill in the range */ 771 tab[++c] = v; 772 } while (c < n); 773 #if 1 /* XXX another disgusting compatibility hack */ 774 /* 775 * Alas, the V7 Unix scanf also treats formats 776 * such as [a-c-e] as `the letters a through e'. 777 * This too is permitted by the standard.... 778 */ 779 goto doswitch; 780 #else 781 c = *fmt++; 782 if (c == 0) 783 return (fmt - 1); 784 if (c == ']') 785 return (fmt); 786 #endif 787 break; 788 789 case ']': /* end of scanset */ 790 return (fmt); 791 792 default: /* just another character */ 793 c = n; 794 break; 795 } 796 } 797 /* NOTREACHED */ 798 } 799