freebsd-dev/lib/libc/stdio/vfscanf.c
Andrey A. Chernov 2209d8a27c Back out national digits support, POSIX explicetely disallows it:
The definition of character class digit requires that only ten characters
-the ones defining digits- can be specified; alternate digits (for
example, Hindi or Kanji) cannot be specified here. However, the encoding
may vary if an implementation supports more than one encoding.

The definition of character class xdigit requires that the characters
included in character class digit are included here also and allows for
different symbols for the hexadecimal digits 10 through 15.
2001-11-29 03:03:55 +00:00

809 lines
19 KiB
C

/*-
* Copyright (c) 1990, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Chris Torek.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
*/
#if defined(LIBC_SCCS) && !defined(lint)
#if 0
static char sccsid[] = "@(#)vfscanf.c 8.1 (Berkeley) 6/4/93";
#endif
static const char rcsid[] =
"$FreeBSD$";
#endif /* LIBC_SCCS and not lint */
#include "namespace.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#if __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#include <string.h>
#include "un-namespace.h"
#include "collate.h"
#include "libc_private.h"
#include "local.h"
#define FLOATING_POINT
#ifdef FLOATING_POINT
#include <locale.h>
#include "floatio.h"
#endif
#define BUF 513 /* Maximum length of numeric string. */
/*
* Flags used during conversion.
*/
#define LONG 0x01 /* l: long or double */
#define LONGDBL 0x02 /* L: long double */
#define SHORT 0x04 /* h: short */
#define SUPPRESS 0x08 /* suppress assignment */
#define POINTER 0x10 /* weird %p pointer (`fake hex') */
#define NOSKIP 0x20 /* do not skip blanks */
#define QUAD 0x400
/*
* The following are used in numeric conversions only:
* SIGNOK, NDIGITS, DPTOK, and EXPOK are for floating point;
* SIGNOK, NDIGITS, PFXOK, and NZDIGITS are for integral.
*/
#define SIGNOK 0x40 /* +/- is (still) legal */
#define NDIGITS 0x80 /* no digits detected */
#define DPTOK 0x100 /* (float) decimal point is still legal */
#define EXPOK 0x200 /* (float) exponent (e+3, etc) still legal */
#define PFXOK 0x100 /* 0x prefix is (still) legal */
#define NZDIGITS 0x200 /* no zero digits detected */
/*
* Conversion types.
*/
#define CT_CHAR 0 /* %c conversion */
#define CT_CCL 1 /* %[...] conversion */
#define CT_STRING 2 /* %s conversion */
#define CT_INT 3 /* integer, i.e., strtoq or strtouq */
#define CT_FLOAT 4 /* floating, i.e., strtod */
#define u_char unsigned char
#define u_long unsigned long
static u_char *__sccl(char *, u_char *);
/*
* __vfscanf - MT-safe version
*/
int
__vfscanf(FILE *fp, char const *fmt0, va_list ap)
{
int ret;
FLOCKFILE(fp);
ret = __svfscanf(fp, fmt0, ap);
FUNLOCKFILE(fp);
return (ret);
}
/*
* __svfscanf - non-MT-safe version of __vfscanf
*/
int
__svfscanf(FILE *fp, char const *fmt0, va_list ap)
{
u_char *fmt = (u_char *)fmt0;
int c; /* character from format, or conversion */
size_t width; /* field width, or 0 */
char *p; /* points into all kinds of strings */
int n; /* handy integer */
int flags; /* flags as defined above */
char *p0; /* saves original value of p when necessary */
int nassigned; /* number of fields assigned */
int nconversions; /* number of conversions */
int nread; /* number of characters consumed from fp */
int base; /* base argument to strtoq/strtouq */
u_quad_t(*ccfn)(); /* conversion function (strtoq/strtouq) */
char ccltab[256]; /* character class table for %[...] */
char buf[BUF]; /* buffer for numeric conversions */
/* `basefix' is used to avoid `if' tests in the integer scanner */
static short basefix[17] =
{ 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
#ifdef FLOATING_POINT
char decimal_point = localeconv()->decimal_point[0];
#endif
nassigned = 0;
nconversions = 0;
nread = 0;
base = 0; /* XXX just to keep gcc happy */
ccfn = NULL; /* XXX just to keep gcc happy */
for (;;) {
c = *fmt++;
if (c == 0)
return (nassigned);
if (isspace(c)) {
while ((fp->_r > 0 || __srefill(fp) == 0) && isspace(*fp->_p))
nread++, fp->_r--, fp->_p++;
continue;
}
if (c != '%')
goto literal;
width = 0;
flags = 0;
/*
* switch on the format. continue if done;
* break once format type is derived.
*/
again: c = *fmt++;
switch (c) {
case '%':
literal:
if (fp->_r <= 0 && __srefill(fp))
goto input_failure;
if (*fp->_p != c)
goto match_failure;
fp->_r--, fp->_p++;
nread++;
continue;
case '*':
flags |= SUPPRESS;
goto again;
case 'l':
flags |= LONG;
goto again;
case 'q':
flags |= QUAD;
goto again;
case 'L':
flags |= LONGDBL;
goto again;
case 'h':
flags |= SHORT;
goto again;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
width = width * 10 + c - '0';
goto again;
/*
* Conversions.
* Those marked `compat' are for 4.[123]BSD compatibility.
*
* (According to ANSI, E and X formats are supposed
* to the same as e and x. Sorry about that.)
*/
case 'D': /* compat */
flags |= LONG;
/* FALLTHROUGH */
case 'd':
c = CT_INT;
ccfn = (u_quad_t (*)())strtoq;
base = 10;
break;
case 'i':
c = CT_INT;
ccfn = (u_quad_t (*)())strtoq;
base = 0;
break;
case 'O': /* compat */
flags |= LONG;
/* FALLTHROUGH */
case 'o':
c = CT_INT;
ccfn = strtouq;
base = 8;
break;
case 'u':
c = CT_INT;
ccfn = strtouq;
base = 10;
break;
case 'X': /* compat XXX */
flags |= LONG;
/* FALLTHROUGH */
case 'x':
flags |= PFXOK; /* enable 0x prefixing */
c = CT_INT;
ccfn = strtouq;
base = 16;
break;
#ifdef FLOATING_POINT
case 'E': /* compat XXX */
case 'F': /* compat */
flags |= LONG;
/* FALLTHROUGH */
case 'e': case 'f': case 'g':
c = CT_FLOAT;
break;
#endif
case 's':
c = CT_STRING;
break;
case '[':
fmt = __sccl(ccltab, fmt);
flags |= NOSKIP;
c = CT_CCL;
break;
case 'c':
flags |= NOSKIP;
c = CT_CHAR;
break;
case 'p': /* pointer format is like hex */
flags |= POINTER | PFXOK;
c = CT_INT;
ccfn = strtouq;
base = 16;
break;
case 'n':
nconversions++;
if (flags & SUPPRESS) /* ??? */
continue;
if (flags & SHORT)
*va_arg(ap, short *) = nread;
else if (flags & LONG)
*va_arg(ap, long *) = nread;
else if (flags & QUAD)
*va_arg(ap, quad_t *) = nread;
else
*va_arg(ap, int *) = nread;
continue;
/*
* Disgusting backwards compatibility hacks. XXX
*/
case '\0': /* compat */
return (EOF);
default: /* compat */
if (isupper(c))
flags |= LONG;
c = CT_INT;
ccfn = (u_quad_t (*)())strtoq;
base = 10;
break;
}
/*
* We have a conversion that requires input.
*/
if (fp->_r <= 0 && __srefill(fp))
goto input_failure;
/*
* Consume leading white space, except for formats
* that suppress this.
*/
if ((flags & NOSKIP) == 0) {
while (isspace(*fp->_p)) {
nread++;
if (--fp->_r > 0)
fp->_p++;
else if (__srefill(fp))
goto input_failure;
}
/*
* Note that there is at least one character in
* the buffer, so conversions that do not set NOSKIP
* ca no longer result in an input failure.
*/
}
/*
* Do the conversion.
*/
switch (c) {
case CT_CHAR:
/* scan arbitrary characters (sets NOSKIP) */
if (width == 0)
width = 1;
if (flags & SUPPRESS) {
size_t sum = 0;
for (;;) {
if ((n = fp->_r) < width) {
sum += n;
width -= n;
fp->_p += n;
if (__srefill(fp)) {
if (sum == 0)
goto input_failure;
break;
}
} else {
sum += width;
fp->_r -= width;
fp->_p += width;
break;
}
}
nread += sum;
} else {
size_t r = fread((void *)va_arg(ap, char *), 1,
width, fp);
if (r == 0)
goto input_failure;
nread += r;
nassigned++;
}
nconversions++;
break;
case CT_CCL:
/* scan a (nonempty) character class (sets NOSKIP) */
if (width == 0)
width = (size_t)~0; /* `infinity' */
/* take only those things in the class */
if (flags & SUPPRESS) {
n = 0;
while (ccltab[*fp->_p]) {
n++, fp->_r--, fp->_p++;
if (--width == 0)
break;
if (fp->_r <= 0 && __srefill(fp)) {
if (n == 0)
goto input_failure;
break;
}
}
if (n == 0)
goto match_failure;
} else {
p0 = p = va_arg(ap, char *);
while (ccltab[*fp->_p]) {
fp->_r--;
*p++ = *fp->_p++;
if (--width == 0)
break;
if (fp->_r <= 0 && __srefill(fp)) {
if (p == p0)
goto input_failure;
break;
}
}
n = p - p0;
if (n == 0)
goto match_failure;
*p = 0;
nassigned++;
}
nread += n;
nconversions++;
break;
case CT_STRING:
/* like CCL, but zero-length string OK, & no NOSKIP */
if (width == 0)
width = (size_t)~0;
if (flags & SUPPRESS) {
n = 0;
while (!isspace(*fp->_p)) {
n++, fp->_r--, fp->_p++;
if (--width == 0)
break;
if (fp->_r <= 0 && __srefill(fp))
break;
}
nread += n;
} else {
p0 = p = va_arg(ap, char *);
while (!isspace(*fp->_p)) {
fp->_r--;
*p++ = *fp->_p++;
if (--width == 0)
break;
if (fp->_r <= 0 && __srefill(fp))
break;
}
*p = 0;
nread += p - p0;
nassigned++;
}
nconversions++;
continue;
case CT_INT:
/* scan an integer as if by strtoq/strtouq */
#ifdef hardway
if (width == 0 || width > sizeof(buf) - 1)
width = sizeof(buf) - 1;
#else
/* size_t is unsigned, hence this optimisation */
if (--width > sizeof(buf) - 2)
width = sizeof(buf) - 2;
width++;
#endif
flags |= SIGNOK | NDIGITS | NZDIGITS;
for (p = buf; width; width--) {
c = *fp->_p;
/*
* Switch on the character; `goto ok'
* if we accept it as a part of number.
*/
switch (c) {
/*
* The digit 0 is always legal, but is
* special. For %i conversions, if no
* digits (zero or nonzero) have been
* scanned (only signs), we will have
* base==0. In that case, we should set
* it to 8 and enable 0x prefixing.
* Also, if we have not scanned zero digits
* before this, do not turn off prefixing
* (someone else will turn it off if we
* have scanned any nonzero digits).
*/
case '0':
if (base == 0) {
base = 8;
flags |= PFXOK;
}
if (flags & NZDIGITS)
flags &= ~(SIGNOK|NZDIGITS|NDIGITS);
else
flags &= ~(SIGNOK|PFXOK|NDIGITS);
goto ok;
/* 1 through 7 always legal */
case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
base = basefix[base];
flags &= ~(SIGNOK | PFXOK | NDIGITS);
goto ok;
/* digits 8 and 9 ok iff decimal or hex */
case '8': case '9':
base = basefix[base];
if (base <= 8)
break; /* not legal here */
flags &= ~(SIGNOK | PFXOK | NDIGITS);
goto ok;
/* letters ok iff hex */
case 'A': case 'B': case 'C':
case 'D': case 'E': case 'F':
case 'a': case 'b': case 'c':
case 'd': case 'e': case 'f':
/* no need to fix base here */
if (base <= 10)
break; /* not legal here */
flags &= ~(SIGNOK | PFXOK | NDIGITS);
goto ok;
/* sign ok only as first character */
case '+': case '-':
if (flags & SIGNOK) {
flags &= ~SIGNOK;
goto ok;
}
break;
/* x ok iff flag still set & 2nd char */
case 'x': case 'X':
if (flags & PFXOK && p == buf + 1) {
base = 16; /* if %i */
flags &= ~PFXOK;
goto ok;
}
break;
}
/*
* If we got here, c is not a legal character
* for a number. Stop accumulating digits.
*/
break;
ok:
/*
* c is legal: store it and look at the next.
*/
*p++ = c;
if (--fp->_r > 0)
fp->_p++;
else if (__srefill(fp))
break; /* EOF */
}
/*
* If we had only a sign, it is no good; push
* back the sign. If the number ends in `x',
* it was [sign] '0' 'x', so push back the x
* and treat it as [sign] '0'.
*/
if (flags & NDIGITS) {
if (p > buf)
(void) __ungetc(*(u_char *)--p, fp);
goto match_failure;
}
c = ((u_char *)p)[-1];
if (c == 'x' || c == 'X') {
--p;
(void) __ungetc(c, fp);
}
if ((flags & SUPPRESS) == 0) {
u_quad_t res;
*p = 0;
res = (*ccfn)(buf, (char **)NULL, base);
if (flags & POINTER)
*va_arg(ap, void **) =
(void *)(u_long)res;
else if (flags & SHORT)
*va_arg(ap, short *) = res;
else if (flags & LONG)
*va_arg(ap, long *) = res;
else if (flags & QUAD)
*va_arg(ap, quad_t *) = res;
else
*va_arg(ap, int *) = res;
nassigned++;
}
nread += p - buf;
nconversions++;
break;
#ifdef FLOATING_POINT
case CT_FLOAT:
/* scan a floating point number as if by strtod */
#ifdef hardway
if (width == 0 || width > sizeof(buf) - 1)
width = sizeof(buf) - 1;
#else
/* size_t is unsigned, hence this optimisation */
if (--width > sizeof(buf) - 2)
width = sizeof(buf) - 2;
width++;
#endif
flags |= SIGNOK | NDIGITS | DPTOK | EXPOK;
for (p = buf; width; width--) {
c = *fp->_p;
/*
* This code mimicks the integer conversion
* code, but is much simpler.
*/
switch (c) {
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
case '8': case '9':
flags &= ~(SIGNOK | NDIGITS);
goto fok;
case '+': case '-':
if (flags & SIGNOK) {
flags &= ~SIGNOK;
goto fok;
}
break;
case 'e': case 'E':
/* no exponent without some digits */
if ((flags&(NDIGITS|EXPOK)) == EXPOK) {
flags =
(flags & ~(EXPOK|DPTOK)) |
SIGNOK | NDIGITS;
goto fok;
}
break;
default:
if ((char)c == decimal_point &&
(flags & DPTOK)) {
flags &= ~(SIGNOK | DPTOK);
goto fok;
}
break;
}
break;
fok:
*p++ = c;
if (--fp->_r > 0)
fp->_p++;
else if (__srefill(fp))
break; /* EOF */
}
/*
* If no digits, might be missing exponent digits
* (just give back the exponent) or might be missing
* regular digits, but had sign and/or decimal point.
*/
if (flags & NDIGITS) {
if (flags & EXPOK) {
/* no digits at all */
while (p > buf)
__ungetc(*(u_char *)--p, fp);
goto match_failure;
}
/* just a bad exponent (e and maybe sign) */
c = *(u_char *)--p;
if (c != 'e' && c != 'E') {
(void) __ungetc(c, fp);/* sign */
c = *(u_char *)--p;
}
(void) __ungetc(c, fp);
}
if ((flags & SUPPRESS) == 0) {
double res;
*p = 0;
/* XXX this loses precision for long doubles. */
res = strtod(buf, (char **) NULL);
if (flags & LONGDBL)
*va_arg(ap, long double *) = res;
else if (flags & LONG)
*va_arg(ap, double *) = res;
else
*va_arg(ap, float *) = res;
nassigned++;
}
nread += p - buf;
nconversions++;
break;
#endif /* FLOATING_POINT */
}
}
input_failure:
return (nconversions != 0 ? nassigned : EOF);
match_failure:
return (nassigned);
}
/*
* Fill in the given table from the scanset at the given format
* (just after `['). Return a pointer to the character past the
* closing `]'. The table has a 1 wherever characters should be
* considered part of the scanset.
*/
static u_char *
__sccl(tab, fmt)
register char *tab;
register u_char *fmt;
{
register int c, n, v, i;
/* first `clear' the whole table */
c = *fmt++; /* first char hat => negated scanset */
if (c == '^') {
v = 1; /* default => accept */
c = *fmt++; /* get new first char */
} else
v = 0; /* default => reject */
/* XXX: Will not work if sizeof(tab*) > sizeof(char) */
(void) memset(tab, v, 256);
if (c == 0)
return (fmt - 1);/* format ended before closing ] */
/*
* Now set the entries corresponding to the actual scanset
* to the opposite of the above.
*
* The first character may be ']' (or '-') without being special;
* the last character may be '-'.
*/
v = 1 - v;
for (;;) {
tab[c] = v; /* take character c */
doswitch:
n = *fmt++; /* and examine the next */
switch (n) {
case 0: /* format ended too soon */
return (fmt - 1);
case '-':
/*
* A scanset of the form
* [01+-]
* is defined as `the digit 0, the digit 1,
* the character +, the character -', but
* the effect of a scanset such as
* [a-zA-Z0-9]
* is implementation defined. The V7 Unix
* scanf treats `a-z' as `the letters a through
* z', but treats `a-a' as `the letter a, the
* character -, and the letter a'.
*
* For compatibility, the `-' is not considerd
* to define a range if the character following
* it is either a close bracket (required by ANSI)
* or is not numerically greater than the character
* we just stored in the table (c).
*/
n = *fmt;
if (n == ']'
|| (__collate_load_error ? n < c :
__collate_range_cmp (n, c) < 0
)
) {
c = '-';
break; /* resume the for(;;) */
}
fmt++;
/* fill in the range */
if (__collate_load_error) {
do {
tab[++c] = v;
} while (c < n);
} else {
for (i = 0; i < 256; i ++)
if ( __collate_range_cmp (c, i) < 0
&& __collate_range_cmp (i, n) <= 0
)
tab[i] = v;
}
#if 1 /* XXX another disgusting compatibility hack */
c = n;
/*
* Alas, the V7 Unix scanf also treats formats
* such as [a-c-e] as `the letters a through e'.
* This too is permitted by the standard....
*/
goto doswitch;
#else
c = *fmt++;
if (c == 0)
return (fmt - 1);
if (c == ']')
return (fmt);
#endif
break;
case ']': /* end of scanset */
return (fmt);
default: /* just another character */
c = n;
break;
}
}
/* NOTREACHED */
}