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