/* * Copyright (c) 2018 Jiri Svoboda * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @addtogroup libc * @{ */ /** * @file * @brief Formatted input (scanf family) */ #include #include <_bits/ssize_t.h> #include #include #include #include #include #include #include #include #include #include "../private/scanf.h" typedef enum { /** No length modifier */ lm_none, /** 'hh' */ lm_hh, /** 'h' */ lm_h, /** 'l' */ lm_l, /** 'll' */ lm_ll, /** 'j' */ lm_j, /** 'z' */ lm_z, /** 't' */ lm_t, /** 'L' */ lm_L } lenmod_t; typedef enum { /** Unknown */ cs_unknown, /** 'd' */ cs_decimal, /** 'i' */ cs_int, /** 'o' */ cs_octal, /** 'u' */ cs_udecimal, /** 'x', 'X' */ cs_hex, /** 'a', 'e', 'f', 'g', 'A', 'E', 'F', 'G' */ cs_float, /** 'c' */ cs_char, /** 's' */ cs_str, /* [...] */ cs_set, /* 'p' */ cs_ptr, /* 'n' */ cs_numchar, /* '%' */ cs_percent } cvtspcr_t; /** Conversion specification */ typedef struct { /** Suppress assignment */ bool noassign; /** Allocate memory for string (GNU extension) */ bool memalloc; /** @c true if width field is valid */ bool have_width; /** Width */ size_t width; /** Length modifier */ lenmod_t lenmod; /** Conversion specifier */ cvtspcr_t spcr; /** Scan set (if spc == cs_set) */ const char *scanset; } cvtspec_t; /** Buffer for writing strings */ typedef struct { /** Buffer */ char *buf; /** Place where to store pointer to the buffer */ char **pptr; /** Allocating memory for caller */ bool memalloc; /** Current size of allocated buffer */ size_t size; } strbuf_t; /** Wrapper needed to pass va_list around by reference in a portable fashion */ typedef struct { va_list ap; } va_encaps_t; static int digit_value(char digit) { switch (digit) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'a': case 'A': return 10; case 'b': case 'B': return 11; case 'c': case 'C': return 12; case 'd': case 'D': return 13; case 'e': case 'E': return 14; case 'f': case 'F': return 15; default: assert(false); } } static void cvtspec_parse(const char **fmt, cvtspec_t *spec) { spec->scanset = NULL; spec->width = 0; /* Assignment suppresion */ if (**fmt == '*') { spec->noassign = true; ++(*fmt); } else { spec->noassign = false; } /* Memory allocation */ if (**fmt == 'm') { spec->memalloc = true; ++(*fmt); } else { spec->memalloc = false; } /* Width specifier */ if (isdigit(**fmt)) { spec->have_width = true; assert(**fmt != '0'); spec->width = 0; while (isdigit(**fmt)) { spec->width *= 10; spec->width += digit_value(**fmt); ++(*fmt); } } else { spec->have_width = false; } /* Length modifier */ switch (**fmt) { case 'h': ++(*fmt); if (**fmt == 'h') { spec->lenmod = lm_hh; ++(*fmt); } else { spec->lenmod = lm_h; } break; case 'l': ++(*fmt); if (**fmt == 'l') { spec->lenmod = lm_ll; ++(*fmt); } else { spec->lenmod = lm_l; } break; case 'j': ++(*fmt); spec->lenmod = lm_j; break; case 'z': ++(*fmt); spec->lenmod = lm_z; break; case 't': ++(*fmt); spec->lenmod = lm_t; break; case 'L': ++(*fmt); spec->lenmod = lm_L; break; default: spec->lenmod = lm_none; break; } /* Conversion specifier */ switch (**fmt) { case 'd': ++(*fmt); spec->spcr = cs_decimal; break; case 'i': ++(*fmt); spec->spcr = cs_int; break; case 'o': ++(*fmt); spec->spcr = cs_octal; break; case 'u': ++(*fmt); spec->spcr = cs_udecimal; break; case 'x': case 'X': ++(*fmt); spec->spcr = cs_hex; break; case 'a': case 'e': case 'f': case 'g': case 'A': case 'E': case 'F': case 'G': ++(*fmt); spec->spcr = cs_float; break; case 'c': ++(*fmt); spec->spcr = cs_char; break; case 's': ++(*fmt); spec->spcr = cs_str; break; case '[': ++(*fmt); spec->spcr = cs_set; spec->scanset = *fmt; while (**fmt != ']' && **fmt != '\0') ++(*fmt); if (**fmt == ']') ++(*fmt); break; case 'p': ++(*fmt); spec->spcr = cs_ptr; break; case 'n': ++(*fmt); spec->spcr = cs_numchar; break; case '%': ++(*fmt); spec->spcr = cs_percent; break; default: assert(false); spec->spcr = cs_unknown; break; } } /** Initialize string buffer. * * String buffer is used to write characters from a string conversion. * The buffer can be provided by caller or dynamically allocated * (and grown). * * Initialize the string buffer @a strbuf. If @a spec->noassign,is true, * set the buffer pointer to NULL. Otherwise, if @a spec->memalloc is true, * allocate a buffer and read an argument of type char ** designating * a place where the pointer should be stored. If @a spec->memalloc is false, * read an argument of type char * and use it as a destination to write * the characters to. * * @param strbuf String buffer to initialize * @param cvtspec Conversion specification (noassign, memalloc) * @param ap Argument list to read pointer from * * @return EOK on success, ENOMEM if out of memory */ static errno_t strbuf_init(strbuf_t *strbuf, cvtspec_t *spec, va_encaps_t *va) { if (spec->noassign) { strbuf->memalloc = false; strbuf->buf = NULL; strbuf->pptr = NULL; strbuf->size = 0; return EOK; } if (spec->memalloc) { /* Allocate memory for caller */ strbuf->memalloc = true; strbuf->size = 1; strbuf->buf = malloc(strbuf->size); if (strbuf->buf == NULL) return ENOMEM; /* * Save pointer to allocated buffer to caller-provided * location */ strbuf->pptr = va_arg(va->ap, char **); *strbuf->pptr = strbuf->buf; } else { /* Caller-provided buffer */ strbuf->memalloc = false; strbuf->size = 0; strbuf->buf = va_arg(va->ap, char *); strbuf->pptr = NULL; } return EOK; } /** Write character at the specified position in a string buffer. * * The buffer is enlarged if necessary. * * @param strbuf String buffer * @param idx Character position (starting from 0) * @param c Character to write * * @return EOK on sucess, ENOMEM if out of memory */ static errno_t strbuf_write(strbuf_t *strbuf, size_t idx, char c) { if (strbuf->memalloc && idx >= strbuf->size) { /* Enlarge buffer */ strbuf->size = strbuf->size * 2; strbuf->buf = realloc(strbuf->buf, strbuf->size); *strbuf->pptr = strbuf->buf; if (strbuf->buf == NULL) return ENOMEM; } if (strbuf->buf != NULL) strbuf->buf[idx] = c; return EOK; } /** Get character from stream, keeping count of number of characters read. * * @param f Stream * @param numchar Pointer to counter of characters read * @return Character on success, EOF on error */ static int __fgetc(FILE *f, int *numchar) { int c; c = fgetc(f); if (c == EOF) return EOF; ++(*numchar); return c; } /** Unget character to stream, keeping count of number of characters read. * * @param c Character * @param f Stream * @param numchar Pointer to counter of characters read * * @return @a c on success, EOF on failure */ static int __ungetc(int c, FILE *f, int *numchar) { int rc; rc = ungetc(c, f); if (rc == EOF) return EOF; --(*numchar); return rc; } /* Skip whitespace in input stream */ static errno_t vfscanf_skip_ws(FILE *f, int *numchar) { int c; c = __fgetc(f, numchar); if (c == EOF) return EIO; while (isspace(c)) { c = __fgetc(f, numchar); if (c == EOF) return EIO; } if (c == EOF) return EIO; __ungetc(c, f, numchar); return EOK; } /* Match whitespace. */ static errno_t vfscanf_match_ws(FILE *f, int *numchar, const char **fmt) { errno_t rc; rc = vfscanf_skip_ws(f, numchar); if (rc == EOF) return EIO; ++(*fmt); return EOK; } /** Read intmax_t integer from file. * * @param f Input file * @param numchar Pointer to counter of characters read * @param base Numeric base (0 means detect using prefix) * @param width Maximum field with in characters * @param dest Place to store result * @return EOK on success, EIO on I/O error, EINVAL if input is not valid */ static errno_t __fstrtoimax(FILE *f, int *numchar, int base, size_t width, intmax_t *dest) { errno_t rc; int c; intmax_t v; int digit; int sign; rc = vfscanf_skip_ws(f, numchar); if (rc == EIO) return EIO; c = __fgetc(f, numchar); if (c == EOF) return EIO; if (c == '+' || c == '-') { /* Sign */ sign = (c == '-') ? -1 : +1; c = __fgetc(f, numchar); if (c == EOF) return EIO; --width; } else { sign = 1; } if (!isdigit(c) || width < 1) { __ungetc(c, f, numchar); return EINVAL; } if (base == 0) { /* Base prefix */ if (c == '0') { c = __fgetc(f, numchar); if (c == EOF) return EIO; --width; if (width > 0 && (c == 'x' || c == 'X')) { --width; c = __fgetc(f, numchar); if (c == EOF) return EIO; if (width > 0 && isxdigit(c)) { base = 16; } else { *dest = 0; return EOK; } } else { base = 8; if (width == 0) { *dest = 0; return EOK; } } } else { base = 10; } } /* Value */ v = 0; do { digit = digit_value(c); if (digit >= base) break; v = v * base + digit; c = __fgetc(f, numchar); --width; } while (width > 0 && isxdigit(c)); if (c != EOF) __ungetc(c, f, numchar); *dest = sign * v; return EOK; } /** Read uintmax_t unsigned integer from file. * * @param f Input file * @param numchar Pointer to counter of characters read * @param base Numeric base (0 means detect using prefix) * @param width Maximum field with in characters * @param dest Place to store result * @return EOK on success, EIO on I/O error, EINVAL if input is not valid */ static errno_t __fstrtoumax(FILE *f, int *numchar, int base, size_t width, uintmax_t *dest) { errno_t rc; int c; uintmax_t v; int digit; rc = vfscanf_skip_ws(f, numchar); if (rc == EIO) return EIO; c = __fgetc(f, numchar); if (c == EOF) return EIO; if (!isdigit(c) || width < 1) { __ungetc(c, f, numchar); return EINVAL; } if (base == 0) { /* Base prefix */ if (c == '0') { c = __fgetc(f, numchar); if (c == EOF) return EIO; --width; if (width > 0 && (c == 'x' || c == 'X')) { --width; c = __fgetc(f, numchar); if (c == EOF) return EIO; if (width > 0 && isxdigit(c)) { base = 16; } else { *dest = 0; return EOK; } } else { base = 8; if (width == 0) { *dest = 0; return EOK; } } } else { base = 10; } } /* Value */ v = 0; do { digit = digit_value(c); if (digit >= base) break; v = v * base + digit; c = __fgetc(f, numchar); --width; } while (width > 0 && isxdigit(c)); if (c != EOF) __ungetc(c, f, numchar); *dest = v; return EOK; } /** Read long double from file. * * @param f Input file * @param numchar Pointer to counter of characters read * @param width Maximum field with in characters * @param dest Place to store result * @return EOK on success, EIO on I/O error, EINVAL if input is not valid */ errno_t __fstrtold(FILE *f, int *numchar, size_t width, long double *dest) { errno_t rc; int c; long double v; int digit; int sign; int base; int efactor; int eadd; int eadj; int exp; int expsign; rc = vfscanf_skip_ws(f, numchar); if (rc == EIO) return EIO; c = __fgetc(f, numchar); if (c == EOF) return EIO; if (c == '+' || c == '-') { /* Sign */ sign = (c == '-') ? -1 : +1; c = __fgetc(f, numchar); if (c == EOF) return EIO; --width; } else { sign = 1; } if (!isdigit(c) || width < 1) { __ungetc(c, f, numchar); return EINVAL; } /* * Default is base 10 */ /* Significand is in base 10 */ base = 10; /* e+1 multiplies number by ten */ efactor = 10; /* Adjust exp. by one for each fractional digit */ eadd = 1; /* Base prefix */ if (c == '0') { c = __fgetc(f, numchar); if (c == EOF) return EIO; --width; if (width > 0 && (c == 'x' || c == 'X')) { --width; c = __fgetc(f, numchar); if (width > 0 && isxdigit(c)) { /* Significand is in base 16 */ base = 16; /* p+1 multiplies number by two */ efactor = 2; /* * Adjust exponent by 4 for each * fractional digit */ eadd = 4; } else { *dest = 0; return EOK; } } } /* Value */ v = 0; do { digit = digit_value(c); if (digit >= base) break; v = v * base + digit; c = __fgetc(f, numchar); --width; } while (width > 0 && isxdigit(c)); /* Decimal-point */ eadj = 0; if (c == '.' && width > 1) { c = __fgetc(f, numchar); if (c == EOF) return EIO; --width; /* Fractional part */ while (width > 0 && isxdigit(c)) { digit = digit_value(c); if (digit >= base) break; v = v * base + digit; c = __fgetc(f, numchar); --width; eadj -= eadd; } } exp = 0; /* Exponent */ if ((width > 1 && base == 10 && (c == 'e' || c == 'E')) || (width > 1 && base == 16 && (c == 'p' || c == 'P'))) { c = __fgetc(f, numchar); if (c == EOF) return EIO; --width; if (width > 1 && (c == '+' || c == '-')) { /* Exponent sign */ if (c == '+') { expsign = 1; } else { expsign = -1; } c = __fgetc(f, numchar); if (c == EOF) return EIO; --width; } else { expsign = 1; } while (width > 0 && isdigit(c)) { digit = digit_value(c); if (digit >= 10) break; exp = exp * 10 + digit; c = __fgetc(f, numchar); --width; } exp = exp * expsign; } exp += eadj; /* Adjust v for value of exponent */ while (exp > 0) { v = v * efactor; --exp; } while (exp < 0) { v = v / efactor; ++exp; } if (c != EOF) __ungetc(c, f, numchar); *dest = sign * v; return EOK; } /* Read characters from stream */ static errno_t __fgetchars(FILE *f, int *numchar, size_t width, strbuf_t *strbuf, size_t *nread) { size_t cnt; int c; errno_t rc; *nread = 0; for (cnt = 0; cnt < width; cnt++) { c = __fgetc(f, numchar); if (c == EOF) { *nread = cnt; return EIO; } rc = strbuf_write(strbuf, cnt, c); if (rc != EOK) { *nread = cnt; return rc; } } *nread = cnt; return EOK; } /* Read non-whitespace string from stream */ static errno_t __fgetstr(FILE *f, int *numchar, size_t width, strbuf_t *strbuf, size_t *nread) { size_t cnt; int c; errno_t rc; errno_t rc2; *nread = 0; rc = vfscanf_skip_ws(f, numchar); if (rc == EIO) return EIO; rc = EOK; for (cnt = 0; cnt < width; cnt++) { c = __fgetc(f, numchar); if (c == EOF) { rc = EIO; break; } if (isspace(c)) { __ungetc(c, f, numchar); break; } rc = strbuf_write(strbuf, cnt, c); if (rc != EOK) { *nread = cnt; return rc; } } /* Null-terminate */ rc2 = strbuf_write(strbuf, cnt, '\0'); if (rc2 != EOK) { *nread = cnt; return rc2; } *nread = cnt; return rc; } /** Determine if character is in scanset. * * Note that we support ranges, although that is a GNU extension. * * @param c Character * @param scanset Pointer to scanset * @return @c true iff @a c is in scanset @a scanset. */ static bool is_in_scanset(char c, const char *scanset) { const char *p = scanset; bool inverted = false; char startc; char endc; /* Inverted scanset */ if (*p == '^') { inverted = true; ++p; } /* * Either ']' or '-' at beginning or after '^' loses special meaning. * However, '-' after ']' (or vice versa) does not. */ if (*p == ']') { /* ']' character in scanset */ if (c == ']') return !inverted; ++p; } else if (*p == '-') { /* '-' character in scanset */ if (c == '-') return !inverted; ++p; } /* Remaining characters */ while (*p != '\0' && *p != ']') { /* '-' is a range unless it's the last character in scanset */ if (*p == '-' && p[1] != ']' && p[1] != '\0') { startc = p[-1]; endc = p[1]; if (c >= startc && c <= endc) return !inverted; p += 2; continue; } if (*p == c) return !inverted; ++p; } return inverted; } /* Read string of characters from scanset from stream */ static errno_t __fgetscanstr(FILE *f, int *numchar, size_t width, const char *scanset, strbuf_t *strbuf, size_t *nread) { size_t cnt; int c; errno_t rc; errno_t rc2; rc = EOK; for (cnt = 0; cnt < width; cnt++) { c = __fgetc(f, numchar); if (c == EOF) { rc = EIO; break; } if (!is_in_scanset(c, scanset)) { __ungetc(c, f, numchar); break; } rc = strbuf_write(strbuf, cnt, c); if (rc != EOK) { *nread = cnt; break; } } /* Null-terminate */ rc2 = strbuf_write(strbuf, cnt, '\0'); if (rc2 != EOK) { *nread = cnt; return rc2; } *nread = cnt; return rc; } /** Perform a single conversion. */ static errno_t vfscanf_cvt(FILE *f, const char **fmt, va_encaps_t *va, int *numchar, unsigned *ncvt) { errno_t rc; int c; intmax_t ival; uintmax_t uval; long double fval = 0.0; int *iptr; short *sptr; signed char *scptr; long *lptr; long long *llptr; ssize_t *ssptr; ptrdiff_t *pdptr; intmax_t *imptr; unsigned *uptr; unsigned short *usptr; unsigned char *ucptr; unsigned long *ulptr; unsigned long long *ullptr; float *fptr; double *dptr; long double *ldptr; size_t *szptr; ptrdiff_t *updptr; uintmax_t *umptr; void **pptr; size_t width; cvtspec_t cvtspec; strbuf_t strbuf; size_t nread; ++(*fmt); cvtspec_parse(fmt, &cvtspec); width = cvtspec.have_width ? cvtspec.width : SIZE_MAX; switch (cvtspec.spcr) { case cs_percent: /* Match % character */ rc = vfscanf_skip_ws(f, numchar); if (rc == EOF) return EIO; c = __fgetc(f, numchar); if (c == EOF) return EIO; if (c != '%') { __ungetc(c, f, numchar); return EINVAL; } break; case cs_decimal: /* Decimal integer */ rc = __fstrtoimax(f, numchar, 10, width, &ival); if (rc != EOK) return rc; break; case cs_udecimal: /* Decimal unsigned integer */ rc = __fstrtoumax(f, numchar, 10, width, &uval); if (rc != EOK) return rc; break; case cs_octal: /* Octal unsigned integer */ rc = __fstrtoumax(f, numchar, 8, width, &uval); if (rc != EOK) return rc; break; case cs_hex: /* Hexadecimal unsigned integer */ rc = __fstrtoumax(f, numchar, 16, width, &uval); if (rc != EOK) return rc; break; case cs_float: /* Floating-point value */ rc = __fstrtold(f, numchar, width, &fval); if (rc != EOK) return rc; break; case cs_int: /* Signed integer with base detection */ rc = __fstrtoimax(f, numchar, 0, width, &ival); if (rc != EOK) return rc; break; case cs_ptr: /* Unsigned integer with base detection (need 0xXXXXX) */ rc = __fstrtoumax(f, numchar, 0, width, &uval); if (rc != EOK) return rc; break; case cs_char: /* Characters */ rc = strbuf_init(&strbuf, &cvtspec, va); if (rc != EOK) return rc; width = cvtspec.have_width ? cvtspec.width : 1; rc = __fgetchars(f, numchar, width, &strbuf, &nread); if (rc != EOK) { if (rc == ENOMEM) return EIO; assert(rc == EIO); /* If we have at least one char, we succeeded */ if (nread > 0) ++(*ncvt); return EIO; } break; case cs_str: /* Non-whitespace string */ rc = strbuf_init(&strbuf, &cvtspec, va); if (rc != EOK) return rc; width = cvtspec.have_width ? cvtspec.width : SIZE_MAX; rc = __fgetstr(f, numchar, width, &strbuf, &nread); if (rc != EOK) { if (rc == ENOMEM) return EIO; assert(rc == EIO); /* If we have at least one char, we succeeded */ if (nread > 0) ++(*ncvt); return EIO; } break; case cs_set: /* String of characters from scanset */ rc = strbuf_init(&strbuf, &cvtspec, va); if (rc != EOK) return rc; width = cvtspec.have_width ? cvtspec.width : SIZE_MAX; rc = __fgetscanstr(f, numchar, width, cvtspec.scanset, &strbuf, &nread); if (rc != EOK) { if (rc == ENOMEM) return EIO; assert(rc == EIO); /* If we have at least one char, we succeeded */ if (nread > 0) ++(*ncvt); return EIO; } break; case cs_numchar: break; default: return EINVAL; } /* Assignment */ if (cvtspec.noassign) goto skip_assign; switch (cvtspec.spcr) { case cs_percent: break; case cs_decimal: case cs_int: switch (cvtspec.lenmod) { case lm_none: iptr = va_arg(va->ap, int *); *iptr = ival; break; case lm_hh: scptr = va_arg(va->ap, signed char *); *scptr = ival; break; case lm_h: sptr = va_arg(va->ap, short *); *sptr = ival; break; case lm_l: lptr = va_arg(va->ap, long *); *lptr = ival; break; case lm_ll: llptr = va_arg(va->ap, long long *); *llptr = ival; break; case lm_j: imptr = va_arg(va->ap, intmax_t *); *imptr = ival; break; case lm_z: ssptr = va_arg(va->ap, ssize_t *); *ssptr = ival; break; case lm_t: pdptr = va_arg(va->ap, ptrdiff_t *); *pdptr = ival; break; default: assert(false); } ++(*ncvt); break; case cs_udecimal: case cs_octal: case cs_hex: switch (cvtspec.lenmod) { case lm_none: uptr = va_arg(va->ap, unsigned *); *uptr = uval; break; case lm_hh: ucptr = va_arg(va->ap, unsigned char *); *ucptr = uval; break; case lm_h: usptr = va_arg(va->ap, unsigned short *); *usptr = uval; break; case lm_l: ulptr = va_arg(va->ap, unsigned long *); *ulptr = uval; break; case lm_ll: ullptr = va_arg(va->ap, unsigned long long *); *ullptr = uval; break; case lm_j: umptr = va_arg(va->ap, uintmax_t *); *umptr = uval; break; case lm_z: szptr = va_arg(va->ap, size_t *); *szptr = uval; break; case lm_t: updptr = va_arg(va->ap, ptrdiff_t *); *updptr = uval; break; default: assert(false); } ++(*ncvt); break; case cs_float: switch (cvtspec.lenmod) { case lm_none: fptr = va_arg(va->ap, float *); *fptr = fval; break; case lm_l: dptr = va_arg(va->ap, double *); *dptr = fval; break; case lm_L: ldptr = va_arg(va->ap, long double *); *ldptr = fval; break; default: assert(false); } ++(*ncvt); break; case cs_ptr: pptr = va_arg(va->ap, void *); *pptr = (void *)(uintptr_t)uval; ++(*ncvt); break; case cs_char: ++(*ncvt); break; case cs_str: ++(*ncvt); break; case cs_set: ++(*ncvt); break; case cs_numchar: /* Store number of characters read so far. */ iptr = va_arg(va->ap, int *); *iptr = *numchar; /* No incrementing of ncvt */ break; default: return EINVAL; } skip_assign: return EOK; } int vfscanf(FILE *f, const char *fmt, va_list ap) { const char *cp; int c; unsigned ncvt; int numchar; bool input_error = false; va_encaps_t va; int rc; va_copy(va.ap, ap); ncvt = 0; numchar = 0; cp = fmt; while (*cp != '\0') { if (isspace(*cp)) { /* Whitespace */ rc = vfscanf_match_ws(f, &numchar, &cp); if (rc == EIO) { input_error = true; break; } assert(rc == EOK); } else if (*cp == '%') { /* Conversion specification */ rc = vfscanf_cvt(f, &cp, &va, &numchar, &ncvt); if (rc == EIO) { /* Input error */ input_error = true; break; } /* Other error */ if (rc != EOK) break; } else { /* Match specific character */ c = __fgetc(f, &numchar); if (c == EOF) { input_error = true; break; } if (c != *cp) { __ungetc(c, f, &numchar); break; } ++cp; } } if (input_error && ncvt == 0) return EOF; return ncvt; } int fscanf(FILE *f, const char *fmt, ...) { va_list args; int rc; va_start(args, fmt); rc = vfscanf(f, fmt, args); va_end(args); return rc; } int vscanf(const char *fmt, va_list ap) { return vfscanf(stdin, fmt, ap); } int scanf(const char *fmt, ...) { va_list args; int rc; va_start(args, fmt); rc = vscanf(fmt, args); va_end(args); return rc; } /** @} */