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