1 /*
2 * Copyright (c) 2000-2001, 2004 Sendmail, Inc. and its suppliers.
3 * All rights reserved.
4 * Copyright (c) 1990, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Chris Torek.
9 *
10 * By using this file, you agree to the terms and conditions set
11 * forth in the LICENSE file which can be found at the top level of
12 * the sendmail distribution.
13 */
14
15 #pragma ident "%Z%%M% %I% %E% SMI"
16
17 #include <sm/gen.h>
18 SM_IDSTR(id, "@(#)$Id: vfscanf.c,v 1.54 2006/10/12 22:03:52 ca Exp $")
19
20 #include <ctype.h>
21 #include <stdlib.h>
22 #include <errno.h>
23 #include <setjmp.h>
24 #include <sm/time.h>
25 #include <sm/varargs.h>
26 #include <sm/config.h>
27 #include <sm/io.h>
28 #include <sm/signal.h>
29 #include <sm/clock.h>
30 #include <sm/string.h>
31 #include "local.h"
32
33 #define BUF 513 /* Maximum length of numeric string. */
34
35 /* Flags used during conversion. */
36 #define LONG 0x01 /* l: long or double */
37 #define SHORT 0x04 /* h: short */
38 #define QUAD 0x08 /* q: quad (same as ll) */
39 #define SUPPRESS 0x10 /* suppress assignment */
40 #define POINTER 0x20 /* weird %p pointer (`fake hex') */
41 #define NOSKIP 0x40 /* do not skip blanks */
42
43 /*
44 ** The following are used in numeric conversions only:
45 ** SIGNOK, NDIGITS, DPTOK, and EXPOK are for floating point;
46 ** SIGNOK, NDIGITS, PFXOK, and NZDIGITS are for integral.
47 */
48
49 #define SIGNOK 0x080 /* +/- is (still) legal */
50 #define NDIGITS 0x100 /* no digits detected */
51
52 #define DPTOK 0x200 /* (float) decimal point is still legal */
53 #define EXPOK 0x400 /* (float) exponent (e+3, etc) still legal */
54
55 #define PFXOK 0x200 /* 0x prefix is (still) legal */
56 #define NZDIGITS 0x400 /* no zero digits detected */
57
58 /* Conversion types. */
59 #define CT_CHAR 0 /* %c conversion */
60 #define CT_CCL 1 /* %[...] conversion */
61 #define CT_STRING 2 /* %s conversion */
62 #define CT_INT 3 /* integer, i.e., strtoll or strtoull */
63 #define CT_FLOAT 4 /* floating, i.e., strtod */
64
65 static void scanalrm __P((int));
66 static unsigned char *sm_sccl __P((char *, unsigned char *));
67 static jmp_buf ScanTimeOut;
68
69 /*
70 ** SCANALRM -- handler when timeout activated for sm_io_vfscanf()
71 **
72 ** Returns flow of control to where setjmp(ScanTimeOut) was set.
73 **
74 ** Parameters:
75 ** sig -- unused
76 **
77 ** Returns:
78 ** does not return
79 **
80 ** Side Effects:
81 ** returns flow of control to setjmp(ScanTimeOut).
82 **
83 ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
84 ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
85 ** DOING.
86 */
87
88 /* ARGSUSED0 */
89 static void
scanalrm(sig)90 scanalrm(sig)
91 int sig;
92 {
93 longjmp(ScanTimeOut, 1);
94 }
95
96 /*
97 ** SM_VFSCANF -- convert input into data units
98 **
99 ** Parameters:
100 ** fp -- file pointer for input data
101 ** timeout -- time intvl allowed to complete (milliseconds)
102 ** fmt0 -- format for finding data units
103 ** ap -- vectors for memory location for storing data units
104 **
105 ** Results:
106 ** Success: number of data units assigned
107 ** Failure: SM_IO_EOF
108 */
109
110 int
sm_vfscanf(fp,timeout,fmt0,ap)111 sm_vfscanf(fp, timeout, fmt0, ap)
112 register SM_FILE_T *fp;
113 int SM_NONVOLATILE timeout;
114 char const *fmt0;
115 va_list SM_NONVOLATILE ap;
116 {
117 register unsigned char *SM_NONVOLATILE fmt = (unsigned char *) fmt0;
118 register int c; /* character from format, or conversion */
119 register size_t width; /* field width, or 0 */
120 register char *p; /* points into all kinds of strings */
121 register int n; /* handy integer */
122 register int flags; /* flags as defined above */
123 register char *p0; /* saves original value of p when necessary */
124 int nassigned; /* number of fields assigned */
125 int nread; /* number of characters consumed from fp */
126 int base; /* base argument to strtoll/strtoull */
127
128 /* conversion function (strtoll/strtoull) */
129 ULONGLONG_T (*ccfn) __P((const char *, char **, int));
130 char ccltab[256]; /* character class table for %[...] */
131 char buf[BUF]; /* buffer for numeric conversions */
132 SM_EVENT *evt = NULL;
133
134 /* `basefix' is used to avoid `if' tests in the integer scanner */
135 static short basefix[17] =
136 { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
137
138 if (timeout == SM_TIME_DEFAULT)
139 timeout = fp->f_timeout;
140 if (timeout == SM_TIME_IMMEDIATE)
141 {
142 /*
143 ** Filling the buffer will take time and we are wanted to
144 ** return immediately. So...
145 */
146
147 errno = EAGAIN;
148 return SM_IO_EOF;
149 }
150
151 if (timeout != SM_TIME_FOREVER)
152 {
153 if (setjmp(ScanTimeOut) != 0)
154 {
155 errno = EAGAIN;
156 return SM_IO_EOF;
157 }
158
159 evt = sm_seteventm(timeout, scanalrm, 0);
160 }
161
162 nassigned = 0;
163 nread = 0;
164 base = 0; /* XXX just to keep gcc happy */
165 ccfn = NULL; /* XXX just to keep gcc happy */
166 for (;;)
167 {
168 c = *fmt++;
169 if (c == 0)
170 {
171 if (evt != NULL)
172 sm_clrevent(evt); /* undo our timeout */
173 return nassigned;
174 }
175 if (isspace(c))
176 {
177 while ((fp->f_r > 0 || sm_refill(fp, SM_TIME_FOREVER)
178 == 0) &&
179 isspace(*fp->f_p))
180 nread++, fp->f_r--, fp->f_p++;
181 continue;
182 }
183 if (c != '%')
184 goto literal;
185 width = 0;
186 flags = 0;
187
188 /*
189 ** switch on the format. continue if done;
190 ** break once format type is derived.
191 */
192
193 again: c = *fmt++;
194 switch (c)
195 {
196 case '%':
197 literal:
198 if (fp->f_r <= 0 && sm_refill(fp, SM_TIME_FOREVER))
199 goto input_failure;
200 if (*fp->f_p != c)
201 goto match_failure;
202 fp->f_r--, fp->f_p++;
203 nread++;
204 continue;
205
206 case '*':
207 flags |= SUPPRESS;
208 goto again;
209 case 'h':
210 flags |= SHORT;
211 goto again;
212 case 'l':
213 if (*fmt == 'l')
214 {
215 fmt++;
216 flags |= QUAD;
217 }
218 else
219 {
220 flags |= LONG;
221 }
222 goto again;
223 case 'q':
224 flags |= QUAD;
225 goto again;
226
227 case '0': case '1': case '2': case '3': case '4':
228 case '5': case '6': case '7': case '8': case '9':
229 width = width * 10 + c - '0';
230 goto again;
231
232 /*
233 ** Conversions.
234 ** Those marked `compat' are for 4.[123]BSD compatibility.
235 **
236 ** (According to ANSI, E and X formats are supposed
237 ** to the same as e and x. Sorry about that.)
238 */
239
240 case 'D': /* compat */
241 flags |= LONG;
242 /* FALLTHROUGH */
243 case 'd':
244 c = CT_INT;
245 ccfn = (ULONGLONG_T (*)())sm_strtoll;
246 base = 10;
247 break;
248
249 case 'i':
250 c = CT_INT;
251 ccfn = (ULONGLONG_T (*)())sm_strtoll;
252 base = 0;
253 break;
254
255 case 'O': /* compat */
256 flags |= LONG;
257 /* FALLTHROUGH */
258 case 'o':
259 c = CT_INT;
260 ccfn = sm_strtoull;
261 base = 8;
262 break;
263
264 case 'u':
265 c = CT_INT;
266 ccfn = sm_strtoull;
267 base = 10;
268 break;
269
270 case 'X':
271 case 'x':
272 flags |= PFXOK; /* enable 0x prefixing */
273 c = CT_INT;
274 ccfn = sm_strtoull;
275 base = 16;
276 break;
277
278 case 'E':
279 case 'G':
280 case 'e':
281 case 'f':
282 case 'g':
283 c = CT_FLOAT;
284 break;
285
286 case 's':
287 c = CT_STRING;
288 break;
289
290 case '[':
291 fmt = sm_sccl(ccltab, fmt);
292 flags |= NOSKIP;
293 c = CT_CCL;
294 break;
295
296 case 'c':
297 flags |= NOSKIP;
298 c = CT_CHAR;
299 break;
300
301 case 'p': /* pointer format is like hex */
302 flags |= POINTER | PFXOK;
303 c = CT_INT;
304 ccfn = sm_strtoull;
305 base = 16;
306 break;
307
308 case 'n':
309 if (flags & SUPPRESS) /* ??? */
310 continue;
311 if (flags & SHORT)
312 *SM_VA_ARG(ap, short *) = nread;
313 else if (flags & LONG)
314 *SM_VA_ARG(ap, long *) = nread;
315 else
316 *SM_VA_ARG(ap, int *) = nread;
317 continue;
318
319 /* Disgusting backwards compatibility hacks. XXX */
320 case '\0': /* compat */
321 if (evt != NULL)
322 sm_clrevent(evt); /* undo our timeout */
323 return SM_IO_EOF;
324
325 default: /* compat */
326 if (isupper(c))
327 flags |= LONG;
328 c = CT_INT;
329 ccfn = (ULONGLONG_T (*)()) sm_strtoll;
330 base = 10;
331 break;
332 }
333
334 /* We have a conversion that requires input. */
335 if (fp->f_r <= 0 && sm_refill(fp, SM_TIME_FOREVER))
336 goto input_failure;
337
338 /*
339 ** Consume leading white space, except for formats
340 ** that suppress this.
341 */
342
343 if ((flags & NOSKIP) == 0)
344 {
345 while (isspace(*fp->f_p))
346 {
347 nread++;
348 if (--fp->f_r > 0)
349 fp->f_p++;
350 else if (sm_refill(fp, SM_TIME_FOREVER))
351 goto input_failure;
352 }
353 /*
354 ** Note that there is at least one character in
355 ** the buffer, so conversions that do not set NOSKIP
356 ** can no longer result in an input failure.
357 */
358 }
359
360 /* Do the conversion. */
361 switch (c)
362 {
363 case CT_CHAR:
364 /* scan arbitrary characters (sets NOSKIP) */
365 if (width == 0)
366 width = 1;
367 if (flags & SUPPRESS)
368 {
369 size_t sum = 0;
370 for (;;)
371 {
372 if ((size_t) (n = fp->f_r) < width)
373 {
374 sum += n;
375 width -= n;
376 fp->f_p += n;
377 if (sm_refill(fp,
378 SM_TIME_FOREVER))
379 {
380 if (sum == 0)
381 goto input_failure;
382 break;
383 }
384 }
385 else
386 {
387 sum += width;
388 fp->f_r -= width;
389 fp->f_p += width;
390 break;
391 }
392 }
393 nread += sum;
394 }
395 else
396 {
397 size_t r;
398
399 r = sm_io_read(fp, SM_TIME_FOREVER,
400 (void *) SM_VA_ARG(ap, char *),
401 width);
402 if (r == 0)
403 goto input_failure;
404 nread += r;
405 nassigned++;
406 }
407 break;
408
409 case CT_CCL:
410 /* scan a (nonempty) character class (sets NOSKIP) */
411 if (width == 0)
412 width = (size_t)~0; /* `infinity' */
413
414 /* take only those things in the class */
415 if (flags & SUPPRESS)
416 {
417 n = 0;
418 while (ccltab[*fp->f_p] != '\0')
419 {
420 n++, fp->f_r--, fp->f_p++;
421 if (--width == 0)
422 break;
423 if (fp->f_r <= 0 &&
424 sm_refill(fp, SM_TIME_FOREVER))
425 {
426 if (n == 0) /* XXX how? */
427 goto input_failure;
428 break;
429 }
430 }
431 if (n == 0)
432 goto match_failure;
433 }
434 else
435 {
436 p0 = p = SM_VA_ARG(ap, char *);
437 while (ccltab[*fp->f_p] != '\0')
438 {
439 fp->f_r--;
440 *p++ = *fp->f_p++;
441 if (--width == 0)
442 break;
443 if (fp->f_r <= 0 &&
444 sm_refill(fp, SM_TIME_FOREVER))
445 {
446 if (p == p0)
447 goto input_failure;
448 break;
449 }
450 }
451 n = p - p0;
452 if (n == 0)
453 goto match_failure;
454 *p = 0;
455 nassigned++;
456 }
457 nread += n;
458 break;
459
460 case CT_STRING:
461 /* like CCL, but zero-length string OK, & no NOSKIP */
462 if (width == 0)
463 width = (size_t)~0;
464 if (flags & SUPPRESS)
465 {
466 n = 0;
467 while (!isspace(*fp->f_p))
468 {
469 n++, fp->f_r--, fp->f_p++;
470 if (--width == 0)
471 break;
472 if (fp->f_r <= 0 &&
473 sm_refill(fp, SM_TIME_FOREVER))
474 break;
475 }
476 nread += n;
477 }
478 else
479 {
480 p0 = p = SM_VA_ARG(ap, char *);
481 while (!isspace(*fp->f_p))
482 {
483 fp->f_r--;
484 *p++ = *fp->f_p++;
485 if (--width == 0)
486 break;
487 if (fp->f_r <= 0 &&
488 sm_refill(fp, SM_TIME_FOREVER))
489 break;
490 }
491 *p = 0;
492 nread += p - p0;
493 nassigned++;
494 }
495 continue;
496
497 case CT_INT:
498 /* scan an integer as if by strtoll/strtoull */
499 #if SM_CONF_BROKEN_SIZE_T
500 if (width == 0 || width > sizeof(buf) - 1)
501 width = sizeof(buf) - 1;
502 #else /* SM_CONF_BROKEN_SIZE_T */
503 /* size_t is unsigned, hence this optimisation */
504 if (--width > sizeof(buf) - 2)
505 width = sizeof(buf) - 2;
506 width++;
507 #endif /* SM_CONF_BROKEN_SIZE_T */
508 flags |= SIGNOK | NDIGITS | NZDIGITS;
509 for (p = buf; width > 0; width--)
510 {
511 c = *fp->f_p;
512
513 /*
514 ** Switch on the character; `goto ok'
515 ** if we accept it as a part of number.
516 */
517
518 switch (c)
519 {
520
521 /*
522 ** The digit 0 is always legal, but is
523 ** special. For %i conversions, if no
524 ** digits (zero or nonzero) have been
525 ** scanned (only signs), we will have
526 ** base==0. In that case, we should set
527 ** it to 8 and enable 0x prefixing.
528 ** Also, if we have not scanned zero digits
529 ** before this, do not turn off prefixing
530 ** (someone else will turn it off if we
531 ** have scanned any nonzero digits).
532 */
533
534 case '0':
535 if (base == 0)
536 {
537 base = 8;
538 flags |= PFXOK;
539 }
540 if (flags & NZDIGITS)
541 flags &= ~(SIGNOK|NZDIGITS|NDIGITS);
542 else
543 flags &= ~(SIGNOK|PFXOK|NDIGITS);
544 goto ok;
545
546 /* 1 through 7 always legal */
547 case '1': case '2': case '3':
548 case '4': case '5': case '6': case '7':
549 base = basefix[base];
550 flags &= ~(SIGNOK | PFXOK | NDIGITS);
551 goto ok;
552
553 /* digits 8 and 9 ok iff decimal or hex */
554 case '8': case '9':
555 base = basefix[base];
556 if (base <= 8)
557 break; /* not legal here */
558 flags &= ~(SIGNOK | PFXOK | NDIGITS);
559 goto ok;
560
561 /* letters ok iff hex */
562 case 'A': case 'B': case 'C':
563 case 'D': case 'E': case 'F':
564 case 'a': case 'b': case 'c':
565 case 'd': case 'e': case 'f':
566
567 /* no need to fix base here */
568 if (base <= 10)
569 break; /* not legal here */
570 flags &= ~(SIGNOK | PFXOK | NDIGITS);
571 goto ok;
572
573 /* sign ok only as first character */
574 case '+': case '-':
575 if (flags & SIGNOK)
576 {
577 flags &= ~SIGNOK;
578 goto ok;
579 }
580 break;
581
582 /* x ok iff flag still set & 2nd char */
583 case 'x': case 'X':
584 if (flags & PFXOK && p == buf + 1)
585 {
586 base = 16; /* if %i */
587 flags &= ~PFXOK;
588 goto ok;
589 }
590 break;
591 }
592
593 /*
594 ** If we got here, c is not a legal character
595 ** for a number. Stop accumulating digits.
596 */
597
598 break;
599 ok:
600 /* c is legal: store it and look at the next. */
601 *p++ = c;
602 if (--fp->f_r > 0)
603 fp->f_p++;
604 else if (sm_refill(fp, SM_TIME_FOREVER))
605 break; /* SM_IO_EOF */
606 }
607
608 /*
609 ** If we had only a sign, it is no good; push
610 ** back the sign. If the number ends in `x',
611 ** it was [sign] '0' 'x', so push back the x
612 ** and treat it as [sign] '0'.
613 */
614
615 if (flags & NDIGITS)
616 {
617 if (p > buf)
618 (void) sm_io_ungetc(fp, SM_TIME_DEFAULT,
619 *(unsigned char *)--p);
620 goto match_failure;
621 }
622 c = ((unsigned char *)p)[-1];
623 if (c == 'x' || c == 'X')
624 {
625 --p;
626 (void) sm_io_ungetc(fp, SM_TIME_DEFAULT, c);
627 }
628 if ((flags & SUPPRESS) == 0)
629 {
630 ULONGLONG_T res;
631
632 *p = 0;
633 res = (*ccfn)(buf, (char **)NULL, base);
634 if (flags & POINTER)
635 *SM_VA_ARG(ap, void **) =
636 (void *)(long) res;
637 else if (flags & QUAD)
638 *SM_VA_ARG(ap, LONGLONG_T *) = res;
639 else if (flags & LONG)
640 *SM_VA_ARG(ap, long *) = res;
641 else if (flags & SHORT)
642 *SM_VA_ARG(ap, short *) = res;
643 else
644 *SM_VA_ARG(ap, int *) = res;
645 nassigned++;
646 }
647 nread += p - buf;
648 break;
649
650 case CT_FLOAT:
651 /* scan a floating point number as if by strtod */
652 if (width == 0 || width > sizeof(buf) - 1)
653 width = sizeof(buf) - 1;
654 flags |= SIGNOK | NDIGITS | DPTOK | EXPOK;
655 for (p = buf; width; width--)
656 {
657 c = *fp->f_p;
658
659 /*
660 ** This code mimicks the integer conversion
661 ** code, but is much simpler.
662 */
663
664 switch (c)
665 {
666
667 case '0': case '1': case '2': case '3':
668 case '4': case '5': case '6': case '7':
669 case '8': case '9':
670 flags &= ~(SIGNOK | NDIGITS);
671 goto fok;
672
673 case '+': case '-':
674 if (flags & SIGNOK)
675 {
676 flags &= ~SIGNOK;
677 goto fok;
678 }
679 break;
680 case '.':
681 if (flags & DPTOK)
682 {
683 flags &= ~(SIGNOK | DPTOK);
684 goto fok;
685 }
686 break;
687 case 'e': case 'E':
688
689 /* no exponent without some digits */
690 if ((flags&(NDIGITS|EXPOK)) == EXPOK)
691 {
692 flags =
693 (flags & ~(EXPOK|DPTOK)) |
694 SIGNOK | NDIGITS;
695 goto fok;
696 }
697 break;
698 }
699 break;
700 fok:
701 *p++ = c;
702 if (--fp->f_r > 0)
703 fp->f_p++;
704 else if (sm_refill(fp, SM_TIME_FOREVER))
705 break; /* SM_IO_EOF */
706 }
707
708 /*
709 ** If no digits, might be missing exponent digits
710 ** (just give back the exponent) or might be missing
711 ** regular digits, but had sign and/or decimal point.
712 */
713
714 if (flags & NDIGITS)
715 {
716 if (flags & EXPOK)
717 {
718 /* no digits at all */
719 while (p > buf)
720 (void) sm_io_ungetc(fp,
721 SM_TIME_DEFAULT,
722 *(unsigned char *)--p);
723 goto match_failure;
724 }
725
726 /* just a bad exponent (e and maybe sign) */
727 c = *(unsigned char *) --p;
728 if (c != 'e' && c != 'E')
729 {
730 (void) sm_io_ungetc(fp, SM_TIME_DEFAULT,
731 c); /* sign */
732 c = *(unsigned char *)--p;
733 }
734 (void) sm_io_ungetc(fp, SM_TIME_DEFAULT, c);
735 }
736 if ((flags & SUPPRESS) == 0)
737 {
738 double res;
739
740 *p = 0;
741 res = strtod(buf, (char **) NULL);
742 if (flags & LONG)
743 *SM_VA_ARG(ap, double *) = res;
744 else
745 *SM_VA_ARG(ap, float *) = res;
746 nassigned++;
747 }
748 nread += p - buf;
749 break;
750 }
751 }
752 input_failure:
753 if (evt != NULL)
754 sm_clrevent(evt); /* undo our timeout */
755 return nassigned ? nassigned : -1;
756 match_failure:
757 if (evt != NULL)
758 sm_clrevent(evt); /* undo our timeout */
759 return nassigned;
760 }
761
762 /*
763 ** SM_SCCL -- sequenced character comparison list
764 **
765 ** Fill in the given table from the scanset at the given format
766 ** (just after `['). Return a pointer to the character past the
767 ** closing `]'. The table has a 1 wherever characters should be
768 ** considered part of the scanset.
769 **
770 ** Parameters:
771 ** tab -- array flagging "active" char's to match (returned)
772 ** fmt -- character list (within "[]")
773 **
774 ** Results:
775 */
776
777 static unsigned char *
sm_sccl(tab,fmt)778 sm_sccl(tab, fmt)
779 register char *tab;
780 register unsigned char *fmt;
781 {
782 register int c, n, v;
783
784 /* first `clear' the whole table */
785 c = *fmt++; /* first char hat => negated scanset */
786 if (c == '^')
787 {
788 v = 1; /* default => accept */
789 c = *fmt++; /* get new first char */
790 }
791 else
792 v = 0; /* default => reject */
793
794 /* should probably use memset here */
795 for (n = 0; n < 256; n++)
796 tab[n] = v;
797 if (c == 0)
798 return fmt - 1; /* format ended before closing ] */
799
800 /*
801 ** Now set the entries corresponding to the actual scanset
802 ** to the opposite of the above.
803 **
804 ** The first character may be ']' (or '-') without being special;
805 ** the last character may be '-'.
806 */
807
808 v = 1 - v;
809 for (;;)
810 {
811 tab[c] = v; /* take character c */
812 doswitch:
813 n = *fmt++; /* and examine the next */
814 switch (n)
815 {
816
817 case 0: /* format ended too soon */
818 return fmt - 1;
819
820 case '-':
821 /*
822 ** A scanset of the form
823 ** [01+-]
824 ** is defined as `the digit 0, the digit 1,
825 ** the character +, the character -', but
826 ** the effect of a scanset such as
827 ** [a-zA-Z0-9]
828 ** is implementation defined. The V7 Unix
829 ** scanf treats `a-z' as `the letters a through
830 ** z', but treats `a-a' as `the letter a, the
831 ** character -, and the letter a'.
832 **
833 ** For compatibility, the `-' is not considerd
834 ** to define a range if the character following
835 ** it is either a close bracket (required by ANSI)
836 ** or is not numerically greater than the character
837 ** we just stored in the table (c).
838 */
839
840 n = *fmt;
841 if (n == ']' || n < c)
842 {
843 c = '-';
844 break; /* resume the for(;;) */
845 }
846 fmt++;
847 do
848 {
849 /* fill in the range */
850 tab[++c] = v;
851 } while (c < n);
852 #if 1 /* XXX another disgusting compatibility hack */
853
854 /*
855 ** Alas, the V7 Unix scanf also treats formats
856 ** such as [a-c-e] as `the letters a through e'.
857 ** This too is permitted by the standard....
858 */
859
860 goto doswitch;
861 #else
862 c = *fmt++;
863 if (c == 0)
864 return fmt - 1;
865 if (c == ']')
866 return fmt;
867 break;
868 #endif
869
870 case ']': /* end of scanset */
871 return fmt;
872
873 default: /* just another character */
874 c = n;
875 break;
876 }
877 }
878 /* NOTREACHED */
879 }
880