xref: /freebsd/lib/libc/stdio/vfwscanf.c (revision 2ad756a6)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
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  * Copyright (c) 2011 The FreeBSD Foundation
11  *
12  * Copyright (c) 2023 Dag-Erling Smørgrav
13  *
14  * Portions of this software were developed by David Chisnall
15  * under sponsorship from the FreeBSD Foundation.
16  *
17  * Redistribution and use in source and binary forms, with or without
18  * modification, are permitted provided that the following conditions
19  * are met:
20  * 1. Redistributions of source code must retain the above copyright
21  *    notice, this list of conditions and the following disclaimer.
22  * 2. Redistributions in binary form must reproduce the above copyright
23  *    notice, this list of conditions and the following disclaimer in the
24  *    documentation and/or other materials provided with the distribution.
25  * 3. Neither the name of the University nor the names of its contributors
26  *    may be used to endorse or promote products derived from this software
27  *    without specific prior written permission.
28  *
29  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
30  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
33  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39  * SUCH DAMAGE.
40  */
41 
42 #if 0
43 #if defined(LIBC_SCCS) && !defined(lint)
44 static char sccsid[] = "@(#)vfscanf.c	8.1 (Berkeley) 6/4/93";
45 #endif /* LIBC_SCCS and not lint */
46 #endif
47 #include <sys/cdefs.h>
48 #include "namespace.h"
49 #include <ctype.h>
50 #include <inttypes.h>
51 #include <limits.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <stddef.h>
55 #include <stdarg.h>
56 #include <string.h>
57 #include <wchar.h>
58 #include <wctype.h>
59 #include "un-namespace.h"
60 
61 #include "libc_private.h"
62 #include "local.h"
63 #include "xlocale_private.h"
64 
65 #define	BUF		513	/* Maximum length of numeric string. */
66 
67 /*
68  * Flags used during conversion.
69  */
70 #define	LONG		0x01	/* l: long or double */
71 #define	LONGDBL		0x02	/* L: long double */
72 #define	SHORT		0x04	/* h: short */
73 #define	SUPPRESS	0x08	/* *: suppress assignment */
74 #define	POINTER		0x10	/* p: void * (as hex) */
75 #define	NOSKIP		0x20	/* [ or c: do not skip blanks */
76 #define	LONGLONG	0x400	/* ll: long long (+ deprecated q: quad) */
77 #define	INTMAXT		0x800	/* j: intmax_t */
78 #define	PTRDIFFT	0x1000	/* t: ptrdiff_t */
79 #define	SIZET		0x2000	/* z: size_t */
80 #define	SHORTSHORT	0x4000	/* hh: char */
81 #define	UNSIGNED	0x8000	/* %[oupxX] conversions */
82 
83 /*
84  * Conversion types.
85  */
86 #define	CT_CHAR		0	/* %c conversion */
87 #define	CT_CCL		1	/* %[...] conversion */
88 #define	CT_STRING	2	/* %s conversion */
89 #define	CT_INT		3	/* %[dioupxX] conversion */
90 #define	CT_FLOAT	4	/* %[efgEFG] conversion */
91 
92 #ifndef NO_FLOATING_POINT
93 static int parsefloat(FILE *, wchar_t *, wchar_t *, locale_t);
94 #endif
95 
96 struct ccl {
97 	const wchar_t *start;	/* character class start */
98 	const wchar_t *end;	/* character class end */
99 	int compl;		/* ccl is complemented? */
100 };
101 
102 static __inline int
103 inccl(const struct ccl *ccl, wint_t wi)
104 {
105 
106 	if (ccl->compl) {
107 		return (wmemchr(ccl->start, wi, ccl->end - ccl->start)
108 		    == NULL);
109 	} else {
110 		return (wmemchr(ccl->start, wi, ccl->end - ccl->start) != NULL);
111 	}
112 }
113 
114 /*
115  * Conversion functions are passed a pointer to this object instead of
116  * a real parameter to indicate that the assignment-suppression (*)
117  * flag was specified.  We could use a NULL pointer to indicate this,
118  * but that would mask bugs in applications that call scanf() with a
119  * NULL pointer.
120  */
121 static const int suppress;
122 #define	SUPPRESS_PTR	((void *)&suppress)
123 
124 static const mbstate_t initial_mbs;
125 
126 /*
127  * The following conversion functions return the number of characters consumed,
128  * or -1 on input failure.  Character class conversion returns 0 on match
129  * failure.
130  */
131 
132 static __inline int
133 convert_char(FILE *fp, char * mbp, int width, locale_t locale)
134 {
135 	mbstate_t mbs;
136 	size_t nconv;
137 	wint_t wi;
138 	int n;
139 
140 	n = 0;
141 	mbs = initial_mbs;
142 	while (width-- != 0 && (wi = __fgetwc(fp, locale)) != WEOF) {
143 		if (mbp != SUPPRESS_PTR) {
144 			nconv = wcrtomb(mbp, wi, &mbs);
145 			if (nconv == (size_t)-1)
146 				return (-1);
147 			mbp += nconv;
148 		}
149 		n++;
150 	}
151 	if (n == 0)
152 		return (-1);
153 	return (n);
154 }
155 
156 static __inline int
157 convert_wchar(FILE *fp, wchar_t *wcp, int width, locale_t locale)
158 {
159 	wint_t wi;
160 	int n;
161 
162 	n = 0;
163 	while (width-- != 0 && (wi = __fgetwc(fp, locale)) != WEOF) {
164 		if (wcp != SUPPRESS_PTR)
165 			*wcp++ = (wchar_t)wi;
166 		n++;
167 	}
168 	if (n == 0)
169 		return (-1);
170 	return (n);
171 }
172 
173 static __inline int
174 convert_ccl(FILE *fp, char * mbp, int width, const struct ccl *ccl,
175     locale_t locale)
176 {
177 	mbstate_t mbs;
178 	size_t nconv;
179 	wint_t wi;
180 	int n;
181 
182 	n = 0;
183 	mbs = initial_mbs;
184 	while ((wi = __fgetwc(fp, locale)) != WEOF &&
185 	    width-- != 0 && inccl(ccl, wi)) {
186 		if (mbp != SUPPRESS_PTR) {
187 			nconv = wcrtomb(mbp, wi, &mbs);
188 			if (nconv == (size_t)-1)
189 				return (-1);
190 			mbp += nconv;
191 		}
192 		n++;
193 	}
194 	if (wi != WEOF)
195 		__ungetwc(wi, fp, locale);
196 	if (mbp != SUPPRESS_PTR)
197 		*mbp = 0;
198 	return (n);
199 }
200 
201 static __inline int
202 convert_wccl(FILE *fp, wchar_t *wcp, int width, const struct ccl *ccl,
203     locale_t locale)
204 {
205 	wchar_t *wcp0;
206 	wint_t wi;
207 	int n;
208 
209 	if (wcp == SUPPRESS_PTR) {
210 		n = 0;
211 		while ((wi = __fgetwc(fp, locale)) != WEOF &&
212 		    width-- != 0 && inccl(ccl, wi))
213 			n++;
214 		if (wi != WEOF)
215 			__ungetwc(wi, fp, locale);
216 	} else {
217 		wcp0 = wcp;
218 		while ((wi = __fgetwc(fp, locale)) != WEOF &&
219 		    width-- != 0 && inccl(ccl, wi))
220 			*wcp++ = (wchar_t)wi;
221 		if (wi != WEOF)
222 			__ungetwc(wi, fp, locale);
223 		n = wcp - wcp0;
224 		if (n == 0)
225 			return (0);
226 		*wcp = 0;
227 	}
228 	return (n);
229 }
230 
231 static __inline int
232 convert_string(FILE *fp, char * mbp, int width, locale_t locale)
233 {
234 	mbstate_t mbs;
235 	size_t nconv;
236 	wint_t wi;
237 	int nread;
238 
239 	mbs = initial_mbs;
240 	nread = 0;
241 	while ((wi = __fgetwc(fp, locale)) != WEOF && width-- != 0 &&
242 	    !iswspace(wi)) {
243 		if (mbp != SUPPRESS_PTR) {
244 			nconv = wcrtomb(mbp, wi, &mbs);
245 			if (nconv == (size_t)-1)
246 				return (-1);
247 			mbp += nconv;
248 		}
249 		nread++;
250 	}
251 	if (wi != WEOF)
252 		__ungetwc(wi, fp, locale);
253 	if (mbp != SUPPRESS_PTR)
254 		*mbp = 0;
255 	return (nread);
256 }
257 
258 static __inline int
259 convert_wstring(FILE *fp, wchar_t *wcp, int width, locale_t locale)
260 {
261 	wint_t wi;
262 	int nread;
263 
264 	nread = 0;
265 	if (wcp == SUPPRESS_PTR) {
266 		while ((wi = __fgetwc(fp, locale)) != WEOF &&
267 		    width-- != 0 && !iswspace(wi))
268 			nread++;
269 		if (wi != WEOF)
270 			__ungetwc(wi, fp, locale);
271 	} else {
272 		while ((wi = __fgetwc(fp, locale)) != WEOF &&
273 		    width-- != 0 && !iswspace(wi)) {
274 			*wcp++ = (wchar_t)wi;
275 			nread++;
276 		}
277 		if (wi != WEOF)
278 			__ungetwc(wi, fp, locale);
279 		*wcp = '\0';
280 	}
281 	return (nread);
282 }
283 
284 enum parseint_state {
285 	begin,
286 	havesign,
287 	havezero,
288 	haveprefix,
289 	any,
290 };
291 
292 static __inline int
293 parseint_fsm(wchar_t c, enum parseint_state *state, int *base)
294 {
295 	switch (c) {
296 	case '+':
297 	case '-':
298 		if (*state == begin) {
299 			*state = havesign;
300 			return 1;
301 		}
302 		break;
303 	case '0':
304 		if (*state == begin || *state == havesign) {
305 			*state = havezero;
306 		} else {
307 			*state = any;
308 		}
309 		return 1;
310 	case '1':
311 	case '2':
312 	case '3':
313 	case '4':
314 	case '5':
315 	case '6':
316 	case '7':
317 		if (*state == havezero && *base == 0) {
318 			*base = 8;
319 		}
320 		/* FALL THROUGH */
321 	case '8':
322 	case '9':
323 		if (*state == begin ||
324 		    *state == havesign) {
325 			if (*base == 0) {
326 				*base = 10;
327 			}
328 		}
329 		if (*state == begin ||
330 		    *state == havesign ||
331 		    *state == havezero ||
332 		    *state == haveprefix ||
333 		    *state == any) {
334 			if (*base > c - '0') {
335 				*state = any;
336 				return 1;
337 			}
338 		}
339 		break;
340 	case 'b':
341 		if (*state == havezero) {
342 			if (*base == 0 || *base == 2) {
343 				*state = haveprefix;
344 				*base = 2;
345 				return 1;
346 			}
347 		}
348 		/* FALL THROUGH */
349 	case 'a':
350 	case 'c':
351 	case 'd':
352 	case 'e':
353 	case 'f':
354 		if (*state == begin ||
355 		    *state == havesign ||
356 		    *state == havezero ||
357 		    *state == haveprefix ||
358 		    *state == any) {
359 			if (*base > c - 'a' + 10) {
360 				*state = any;
361 				return 1;
362 			}
363 		}
364 		break;
365 	case 'B':
366 		if (*state == havezero) {
367 			if (*base == 0 || *base == 2) {
368 				*state = haveprefix;
369 				*base = 2;
370 				return 1;
371 			}
372 		}
373 		/* FALL THROUGH */
374 	case 'A':
375 	case 'C':
376 	case 'D':
377 	case 'E':
378 	case 'F':
379 		if (*state == begin ||
380 		    *state == havesign ||
381 		    *state == havezero ||
382 		    *state == haveprefix ||
383 		    *state == any) {
384 			if (*base > c - 'A' + 10) {
385 				*state = any;
386 				return 1;
387 			}
388 		}
389 		break;
390 	case 'x':
391 	case 'X':
392 		if (*state == havezero) {
393 			if (*base == 0 || *base == 16) {
394 				*state = haveprefix;
395 				*base = 16;
396 				return 1;
397 			}
398 		}
399 		break;
400 	}
401 	return 0;
402 }
403 
404 /*
405  * Read an integer, storing it in buf.
406  *
407  * Return 0 on a match failure, and the number of characters read
408  * otherwise.
409  */
410 static __inline int
411 parseint(FILE *fp, wchar_t * __restrict buf, int width, int base,
412     locale_t locale)
413 {
414 	enum parseint_state state = begin;
415 	wchar_t *wcp;
416 	int c;
417 
418 	for (wcp = buf; width; width--) {
419 		c = __fgetwc(fp, locale);
420 		if (c == WEOF)
421 			break;
422 		if (!parseint_fsm(c, &state, &base))
423 			break;
424 		*wcp++ = (wchar_t)c;
425 	}
426 	/*
427 	 * If we only had a sign, push it back.  If we only had a 0b or 0x
428 	 * prefix (possibly preceded by a sign), we view it as "0" and
429 	 * push back the letter.  In all other cases, if we stopped
430 	 * because we read a non-number character, push it back.
431 	 */
432 	if (state == havesign) {
433 		wcp--;
434 		__ungetwc(*wcp, fp, locale);
435 	} else if (state == haveprefix) {
436 		wcp--;
437 		__ungetwc(c, fp, locale);
438 	} else if (width && c != WEOF) {
439 		__ungetwc(c, fp, locale);
440 	}
441 	return (wcp - buf);
442 }
443 
444 /*
445  * MT-safe version.
446  */
447 int
448 vfwscanf_l(FILE * __restrict fp, locale_t locale,
449 		const wchar_t * __restrict fmt, va_list ap)
450 {
451 	int ret;
452 	FIX_LOCALE(locale);
453 
454 	FLOCKFILE_CANCELSAFE(fp);
455 	ORIENT(fp, 1);
456 	ret = __vfwscanf(fp, locale, fmt, ap);
457 	FUNLOCKFILE_CANCELSAFE();
458 	return (ret);
459 }
460 int
461 vfwscanf(FILE * __restrict fp, const wchar_t * __restrict fmt, va_list ap)
462 {
463 	return vfwscanf_l(fp, __get_locale(), fmt, ap);
464 }
465 
466 /*
467  * Non-MT-safe version.
468  */
469 int
470 __vfwscanf(FILE * __restrict fp, locale_t locale,
471 		const wchar_t * __restrict fmt, va_list ap)
472 {
473 #define	GETARG(type)	((flags & SUPPRESS) ? SUPPRESS_PTR : va_arg(ap, type))
474 	wint_t c;		/* character from format, or conversion */
475 	size_t width;		/* field width, or 0 */
476 	int flags;		/* flags as defined above */
477 	int nassigned;		/* number of fields assigned */
478 	int nconversions;	/* number of conversions */
479 	int nr;			/* characters read by the current conversion */
480 	int nread;		/* number of characters consumed from fp */
481 	int base;		/* base argument to conversion function */
482 	struct ccl ccl;		/* character class info */
483 	wchar_t buf[BUF];	/* buffer for numeric conversions */
484 	wint_t wi;		/* handy wint_t */
485 
486 	nassigned = 0;
487 	nconversions = 0;
488 	nread = 0;
489 	ccl.start = ccl.end = NULL;
490 	for (;;) {
491 		c = *fmt++;
492 		if (c == 0)
493 			return (nassigned);
494 		if (iswspace(c)) {
495 			while ((c = __fgetwc(fp, locale)) != WEOF &&
496 			    iswspace_l(c, locale))
497 				nread++;
498 			if (c != WEOF)
499 				__ungetwc(c, fp, locale);
500 			continue;
501 		}
502 		if (c != '%')
503 			goto literal;
504 		width = 0;
505 		flags = 0;
506 		/*
507 		 * switch on the format.  continue if done;
508 		 * break once format type is derived.
509 		 */
510 again:		c = *fmt++;
511 		switch (c) {
512 		case '%':
513 literal:
514 			if ((wi = __fgetwc(fp, locale)) == WEOF)
515 				goto input_failure;
516 			if (wi != c) {
517 				__ungetwc(wi, fp, locale);
518 				goto match_failure;
519 			}
520 			nread++;
521 			continue;
522 
523 		case '*':
524 			flags |= SUPPRESS;
525 			goto again;
526 		case 'j':
527 			flags |= INTMAXT;
528 			goto again;
529 		case 'l':
530 			if (flags & LONG) {
531 				flags &= ~LONG;
532 				flags |= LONGLONG;
533 			} else
534 				flags |= LONG;
535 			goto again;
536 		case 'q':
537 			flags |= LONGLONG;	/* not quite */
538 			goto again;
539 		case 't':
540 			flags |= PTRDIFFT;
541 			goto again;
542 		case 'z':
543 			flags |= SIZET;
544 			goto again;
545 		case 'L':
546 			flags |= LONGDBL;
547 			goto again;
548 		case 'h':
549 			if (flags & SHORT) {
550 				flags &= ~SHORT;
551 				flags |= SHORTSHORT;
552 			} else
553 				flags |= SHORT;
554 			goto again;
555 
556 		case '0': case '1': case '2': case '3': case '4':
557 		case '5': case '6': case '7': case '8': case '9':
558 			width = width * 10 + c - '0';
559 			goto again;
560 
561 		/*
562 		 * Conversions.
563 		 */
564 		case 'B':
565 		case 'b':
566 			c = CT_INT;
567 			flags |= UNSIGNED;
568 			base = 2;
569 			break;
570 
571 		case 'd':
572 			c = CT_INT;
573 			base = 10;
574 			break;
575 
576 		case 'i':
577 			c = CT_INT;
578 			base = 0;
579 			break;
580 
581 		case 'o':
582 			c = CT_INT;
583 			flags |= UNSIGNED;
584 			base = 8;
585 			break;
586 
587 		case 'u':
588 			c = CT_INT;
589 			flags |= UNSIGNED;
590 			base = 10;
591 			break;
592 
593 		case 'X':
594 		case 'x':
595 			c = CT_INT;
596 			flags |= UNSIGNED;
597 			base = 16;
598 			break;
599 
600 #ifndef NO_FLOATING_POINT
601 		case 'A': case 'E': case 'F': case 'G':
602 		case 'a': case 'e': case 'f': case 'g':
603 			c = CT_FLOAT;
604 			break;
605 #endif
606 
607 		case 'S':
608 			flags |= LONG;
609 			/* FALLTHROUGH */
610 		case 's':
611 			c = CT_STRING;
612 			break;
613 
614 		case '[':
615 			ccl.start = fmt;
616 			if (*fmt == '^') {
617 				ccl.compl = 1;
618 				fmt++;
619 			} else
620 				ccl.compl = 0;
621 			if (*fmt == ']')
622 				fmt++;
623 			while (*fmt != '\0' && *fmt != ']')
624 				fmt++;
625 			ccl.end = fmt;
626 			fmt++;
627 			flags |= NOSKIP;
628 			c = CT_CCL;
629 			break;
630 
631 		case 'C':
632 			flags |= LONG;
633 			/* FALLTHROUGH */
634 		case 'c':
635 			flags |= NOSKIP;
636 			c = CT_CHAR;
637 			break;
638 
639 		case 'p':	/* pointer format is like hex */
640 			flags |= POINTER;
641 			c = CT_INT;		/* assumes sizeof(uintmax_t) */
642 			flags |= UNSIGNED;	/*      >= sizeof(uintptr_t) */
643 			base = 16;
644 			break;
645 
646 		case 'n':
647 			if (flags & SUPPRESS)	/* ??? */
648 				continue;
649 			if (flags & SHORTSHORT)
650 				*va_arg(ap, char *) = nread;
651 			else if (flags & SHORT)
652 				*va_arg(ap, short *) = nread;
653 			else if (flags & LONG)
654 				*va_arg(ap, long *) = nread;
655 			else if (flags & LONGLONG)
656 				*va_arg(ap, long long *) = nread;
657 			else if (flags & INTMAXT)
658 				*va_arg(ap, intmax_t *) = nread;
659 			else if (flags & SIZET)
660 				*va_arg(ap, size_t *) = nread;
661 			else if (flags & PTRDIFFT)
662 				*va_arg(ap, ptrdiff_t *) = nread;
663 			else
664 				*va_arg(ap, int *) = nread;
665 			continue;
666 
667 		default:
668 			goto match_failure;
669 
670 		/*
671 		 * Disgusting backwards compatibility hack.	XXX
672 		 */
673 		case '\0':	/* compat */
674 			return (EOF);
675 		}
676 
677 		/*
678 		 * Consume leading white space, except for formats
679 		 * that suppress this.
680 		 */
681 		if ((flags & NOSKIP) == 0) {
682 			while ((wi = __fgetwc(fp, locale)) != WEOF && iswspace(wi))
683 				nread++;
684 			if (wi == WEOF)
685 				goto input_failure;
686 			__ungetwc(wi, fp, locale);
687 		}
688 
689 		/*
690 		 * Do the conversion.
691 		 */
692 		switch (c) {
693 
694 		case CT_CHAR:
695 			/* scan arbitrary characters (sets NOSKIP) */
696 			if (width == 0)
697 				width = 1;
698 			if (flags & LONG) {
699 				nr = convert_wchar(fp, GETARG(wchar_t *), width,
700 				    locale);
701 			} else {
702 				nr = convert_char(fp, GETARG(char *), width,
703 				    locale);
704 			}
705 			if (nr < 0)
706 				goto input_failure;
707 			break;
708 
709 		case CT_CCL:
710 			/* scan a (nonempty) character class (sets NOSKIP) */
711 			if (width == 0)
712 				width = (size_t)~0;	/* `infinity' */
713 			/* take only those things in the class */
714 			if (flags & LONG) {
715 				nr = convert_wccl(fp, GETARG(wchar_t *), width,
716 				    &ccl, locale);
717 			} else {
718 				nr = convert_ccl(fp, GETARG(char *), width,
719 				    &ccl, locale);
720 			}
721 			if (nr <= 0) {
722 				if (nr < 0)
723 					goto input_failure;
724 				else /* nr == 0 */
725 					goto match_failure;
726 			}
727 			break;
728 
729 		case CT_STRING:
730 			/* like CCL, but zero-length string OK, & no NOSKIP */
731 			if (width == 0)
732 				width = (size_t)~0;
733 			if (flags & LONG) {
734 				nr = convert_wstring(fp, GETARG(wchar_t *),
735 				    width, locale);
736 			} else {
737 				nr = convert_string(fp, GETARG(char *), width,
738 				    locale);
739 			}
740 			if (nr < 0)
741 				goto input_failure;
742 			break;
743 
744 		case CT_INT:
745 			/* scan an integer as if by the conversion function */
746 			if (width == 0 || width > sizeof(buf) /
747 			    sizeof(*buf) - 1)
748 				width = sizeof(buf) / sizeof(*buf) - 1;
749 
750 			nr = parseint(fp, buf, width, base, locale);
751 			if (nr == 0)
752 				goto match_failure;
753 			if ((flags & SUPPRESS) == 0) {
754 				uintmax_t res;
755 
756 				buf[nr] = L'\0';
757 				if ((flags & UNSIGNED) == 0)
758 				    res = wcstoimax(buf, NULL, base);
759 				else
760 				    res = wcstoumax(buf, NULL, base);
761 				if (flags & POINTER)
762 					*va_arg(ap, void **) =
763 							(void *)(uintptr_t)res;
764 				else if (flags & SHORTSHORT)
765 					*va_arg(ap, char *) = res;
766 				else if (flags & SHORT)
767 					*va_arg(ap, short *) = res;
768 				else if (flags & LONG)
769 					*va_arg(ap, long *) = res;
770 				else if (flags & LONGLONG)
771 					*va_arg(ap, long long *) = res;
772 				else if (flags & INTMAXT)
773 					*va_arg(ap, intmax_t *) = res;
774 				else if (flags & PTRDIFFT)
775 					*va_arg(ap, ptrdiff_t *) = res;
776 				else if (flags & SIZET)
777 					*va_arg(ap, size_t *) = res;
778 				else
779 					*va_arg(ap, int *) = res;
780 			}
781 			break;
782 
783 #ifndef NO_FLOATING_POINT
784 		case CT_FLOAT:
785 			/* scan a floating point number as if by strtod */
786 			if (width == 0 || width > sizeof(buf) /
787 			    sizeof(*buf) - 1)
788 				width = sizeof(buf) / sizeof(*buf) - 1;
789 			nr = parsefloat(fp, buf, buf + width, locale);
790 			if (nr == 0)
791 				goto match_failure;
792 			if ((flags & SUPPRESS) == 0) {
793 				if (flags & LONGDBL) {
794 					long double res = wcstold(buf, NULL);
795 					*va_arg(ap, long double *) = res;
796 				} else if (flags & LONG) {
797 					double res = wcstod(buf, NULL);
798 					*va_arg(ap, double *) = res;
799 				} else {
800 					float res = wcstof(buf, NULL);
801 					*va_arg(ap, float *) = res;
802 				}
803 			}
804 			break;
805 #endif /* !NO_FLOATING_POINT */
806 		}
807 		if (!(flags & SUPPRESS))
808 			nassigned++;
809 		nread += nr;
810 		nconversions++;
811 	}
812 input_failure:
813 	return (nconversions != 0 ? nassigned : EOF);
814 match_failure:
815 	return (nassigned);
816 }
817 
818 #ifndef NO_FLOATING_POINT
819 static int
820 parsefloat(FILE *fp, wchar_t *buf, wchar_t *end, locale_t locale)
821 {
822 	mbstate_t mbs;
823 	size_t nconv;
824 	wchar_t *commit, *p;
825 	int infnanpos = 0;
826 	enum {
827 		S_START, S_GOTSIGN, S_INF, S_NAN, S_DONE, S_MAYBEHEX,
828 		S_DIGITS, S_FRAC, S_EXP, S_EXPDIGITS
829 	} state = S_START;
830 	wchar_t c;
831 	wchar_t decpt;
832 	_Bool gotmantdig = 0, ishex = 0;
833 
834 	mbs = initial_mbs;
835 	nconv = mbrtowc(&decpt, localeconv()->decimal_point, MB_CUR_MAX, &mbs);
836 	if (nconv == (size_t)-1 || nconv == (size_t)-2)
837 		decpt = '.';	/* failsafe */
838 
839 	/*
840 	 * We set commit = p whenever the string we have read so far
841 	 * constitutes a valid representation of a floating point
842 	 * number by itself.  At some point, the parse will complete
843 	 * or fail, and we will ungetc() back to the last commit point.
844 	 * To ensure that the file offset gets updated properly, it is
845 	 * always necessary to read at least one character that doesn't
846 	 * match; thus, we can't short-circuit "infinity" or "nan(...)".
847 	 */
848 	commit = buf - 1;
849 	c = WEOF;
850 	for (p = buf; p < end; ) {
851 		if ((c = __fgetwc(fp, locale)) == WEOF)
852 			break;
853 reswitch:
854 		switch (state) {
855 		case S_START:
856 			state = S_GOTSIGN;
857 			if (c == '-' || c == '+')
858 				break;
859 			else
860 				goto reswitch;
861 		case S_GOTSIGN:
862 			switch (c) {
863 			case '0':
864 				state = S_MAYBEHEX;
865 				commit = p;
866 				break;
867 			case 'I':
868 			case 'i':
869 				state = S_INF;
870 				break;
871 			case 'N':
872 			case 'n':
873 				state = S_NAN;
874 				break;
875 			default:
876 				state = S_DIGITS;
877 				goto reswitch;
878 			}
879 			break;
880 		case S_INF:
881 			if (infnanpos > 6 ||
882 			    (c != "nfinity"[infnanpos] &&
883 			     c != "NFINITY"[infnanpos]))
884 				goto parsedone;
885 			if (infnanpos == 1 || infnanpos == 6)
886 				commit = p;	/* inf or infinity */
887 			infnanpos++;
888 			break;
889 		case S_NAN:
890 			switch (infnanpos) {
891 			case 0:
892 				if (c != 'A' && c != 'a')
893 					goto parsedone;
894 				break;
895 			case 1:
896 				if (c != 'N' && c != 'n')
897 					goto parsedone;
898 				else
899 					commit = p;
900 				break;
901 			case 2:
902 				if (c != '(')
903 					goto parsedone;
904 				break;
905 			default:
906 				if (c == ')') {
907 					commit = p;
908 					state = S_DONE;
909 				} else if (!iswalnum(c) && c != '_')
910 					goto parsedone;
911 				break;
912 			}
913 			infnanpos++;
914 			break;
915 		case S_DONE:
916 			goto parsedone;
917 		case S_MAYBEHEX:
918 			state = S_DIGITS;
919 			if (c == 'X' || c == 'x') {
920 				ishex = 1;
921 				break;
922 			} else {	/* we saw a '0', but no 'x' */
923 				gotmantdig = 1;
924 				goto reswitch;
925 			}
926 		case S_DIGITS:
927 			if ((ishex && iswxdigit(c)) || iswdigit(c))
928 				gotmantdig = 1;
929 			else {
930 				state = S_FRAC;
931 				if (c != decpt)
932 					goto reswitch;
933 			}
934 			if (gotmantdig)
935 				commit = p;
936 			break;
937 		case S_FRAC:
938 			if (((c == 'E' || c == 'e') && !ishex) ||
939 			    ((c == 'P' || c == 'p') && ishex)) {
940 				if (!gotmantdig)
941 					goto parsedone;
942 				else
943 					state = S_EXP;
944 			} else if ((ishex && iswxdigit(c)) || iswdigit(c)) {
945 				commit = p;
946 				gotmantdig = 1;
947 			} else
948 				goto parsedone;
949 			break;
950 		case S_EXP:
951 			state = S_EXPDIGITS;
952 			if (c == '-' || c == '+')
953 				break;
954 			else
955 				goto reswitch;
956 		case S_EXPDIGITS:
957 			if (iswdigit(c))
958 				commit = p;
959 			else
960 				goto parsedone;
961 			break;
962 		default:
963 			abort();
964 		}
965 		*p++ = c;
966 		c = WEOF;
967 	}
968 
969 parsedone:
970 	if (c != WEOF)
971 		__ungetwc(c, fp, locale);
972 	while (commit < --p)
973 		__ungetwc(*p, fp, locale);
974 	*++commit = '\0';
975 	return (commit - buf);
976 }
977 #endif
978