HelenOS sources

root/uspace/lib/c/generic/stdio/scanf.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. digit_value
  2. cvtspec_parse
  3. strbuf_init
  4. strbuf_write
  5. __fgetc
  6. __ungetc
  7. vfscanf_skip_ws
  8. vfscanf_match_ws
  9. __fstrtoimax
  10. __fstrtoumax
  11. __fstrtold
  12. __fgetchars
  13. __fgetstr
  14. is_in_scanset
  15. __fgetscanstr
  16. vfscanf_cvt
  17. vfscanf
  18. fscanf
  19. vscanf
  20. scanf

/*
 * 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 <assert.h>
#include <_bits/ssize_t.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#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;
}

/** @}
 */

/* [<][>][^][v][top][bottom][index][help] */
HelenOS homepage, sources at GitHub