/* * libslack - https://libslack.org * * Copyright (C) 1999-2004, 2010, 2020-2023 raf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * 20230824 raf */ /* =head1 NAME I - I with a I parameter =head1 SYNOPSIS #include #ifndef HAVE_VSSCANF #include #endif int vsscanf(const char *str, const char *format, va_list args); =head1 DESCRIPTION Similar to I with the variable argument list specified directly as for I. Note that this may not be identical in behaviour to the I on your system because this was implemented from scratch for systems that lack I. So your I and this I share no common code. Your I may support extensions that I does not support. I complies with all of the relevant I requirements for I except: =over 4 =item * C may not be a multi-byte character string; and =item * Scanning a pointer (C<"%p">) may not exactly match the format that your I uses to print pointers on your system. This version accepts pointers as a hexadecimal number, with or without a preceding C<0x>. =back =head1 MT-Level I if and only if no thread calls I. Since locales are inherently non-threadsafe as they are currently defined, this shouldn't be a problem. Just call C once after program initialisation and never again (at least not after creating any threads). If it is a problem, just change Cdecimal_point[0]> in the source to C<'.'>, and it will be I at the expense of losing locale support. =head1 EXAMPLE #include #ifndef HAVE_VSSCANF #include #endif int fdscanf(int fd, const char *format, ...) { va_list args; char buf[BUFSIZ]; ssize_t bytes; int rc; if ((bytes = read(fd, buf, BUFSIZ)) <= 0) return bytes; buf[bytes] = '\0'; va_start(args, format); rc = vsscanf(buf, format, args); va_end(args); return rc; } int main() { int rc, a = 0, b = 0; rc = fdscanf(STDIN_FILENO, "%d %d", &a, &b); printf("rc = %d a = %d b = %d\n", rc, a, b); return (rc == 2) ? EXIT_SUCCESS : EXIT_FAILURE; } =head1 NOTE I warns that: warning: ANSI C does not support the `L' length modifier warning: use of `l' length character with `e' type character However, the I standard (Section 7.9.6.2) states that: "Finally, the conversion specifiers C, C, and C shall be preceded by C if the corresponding argument is a pointer to I rather than a pointer to C, or by C if it is a pointer to I." I have chosen to disregard the I warnings in favour of the standard. If you see the above warnings when compiling the unit tests for I, just ignore them. =head1 SEE ALSO I, I =head1 AUTHOR 20230824 raf =cut */ #include "config.h" #include "std.h" #include int vsscanf(const char *str, const char *format, va_list args) { const char *f, *s; const char point = localeconv()->decimal_point[0]; int cnv = 0; for (s = str, f = format; *f; ++f) { if (*f == '%') { int size = 0; int width = 0; int do_cnv = 1; if (*++f == '*') ++f, do_cnv = 0; for (; isdigit((int)(unsigned int)*f); ++f) width *= 10, width += *f - '0'; if (*f == 'h' || *f == 'l' || *f == 'L') size = *f++; if (*f != '[' && *f != 'c' && *f != 'n') while (isspace((int)(unsigned int)*s)) ++s; #define COPY *b++ = *s++, --width #define MATCH(cond) if (width && (cond)) COPY; #define MATCH_ACTION(cond, action) if (width && (cond)) { COPY; action; } #define MATCHES_ACTION(cond, action) while (width && (cond)) { COPY; action; } #define FAIL (cnv) ? cnv : EOF switch (*f) { case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': case 'p': { static const char types[] = "diouxXp"; static const int bases[] = { 10, 0, 8, 10, 16, 16, 16 }; static const char digitset[] = "0123456789abcdefABCDEF"; static const int setsizes[] = { 10, 0, 0, 0, 0, 0, 0, 0, 8, 0, 10, 0, 0, 0, 0, 0, 22 }; int base = bases[strchr(types, *f) - types]; int setsize; char buf[513]; char *b = buf; int digit = 0; if (width <= 0 || width > 512) width = 512; MATCH(*s == '+' || *s == '-') MATCH_ACTION(*s == '0', digit = 1; MATCH_ACTION((*s == 'x' || *s == 'X') && (base == 0 || base == 16), base = 16) else base = 8; ) setsize = setsizes[base]; MATCHES_ACTION(memchr(digitset, *s, setsize), digit = 1) if (!digit) return FAIL; *b = '\0'; if (do_cnv) { if (*f == 'd' || *f == 'i') { long data = strtol(buf, NULL, base); if (size == 'h') *va_arg(args, short *) = (short)data; else if (size == 'l') *va_arg(args, long *) = data; else *va_arg(args, int *) = (int)data; } else { unsigned long data = strtoul(buf, NULL, base); if (size == 'p') *va_arg(args, void **) = (void *)data; else if (size == 'h') *va_arg(args, unsigned short *) = (unsigned short)data; else if (size == 'l') *va_arg(args, unsigned long *) = data; else *va_arg(args, unsigned int *) = (unsigned int)data; } ++cnv; } break; } case 'e': case 'E': case 'f': case 'g': case 'G': { char buf[513]; char *b = buf; int digit = 0; if (width <= 0 || width > 512) width = 512; MATCH(*s == '+' || *s == '-') MATCHES_ACTION(isdigit((int)(unsigned int)*s), digit = 1) MATCH(*s == point) MATCHES_ACTION(isdigit((int)(unsigned int)*s), digit = 1) MATCHES_ACTION(digit && (*s == 'e' || *s == 'E'), MATCH(*s == '+' || *s == '-') digit = 0; MATCHES_ACTION(isdigit((int)(unsigned int)*s), digit = 1) ) if (!digit) return FAIL; *b = '\0'; if (do_cnv) { double data = strtod(buf, NULL); if (size == 'l') *va_arg(args, double *) = data; else if (size == 'L') *va_arg(args, long double *) = (long double)data; else *va_arg(args, float *) = (float)data; ++cnv; } break; } case 's': { char *arg = va_arg(args, char *); if (width <= 0) width = INT_MAX; while (width-- && *s && !isspace((int)(unsigned int)*s)) if (do_cnv) *arg++ = *s++; if (do_cnv) *arg = '\0', ++cnv; break; } case '[': { char *arg = va_arg(args, char *); int setcomp = 0; size_t setsize; const char *end; if (width <= 0) width = INT_MAX; if (*++f == '^') setcomp = 1, ++f; end = strchr((*f == ']') ? f + 1 : f, ']'); if (!end) return FAIL; /* Could be cnv to match glibc-2.2 */ setsize = end - f; /* But FAIL matches the C standard */ if (!*s) return FAIL; while (width-- && *s) { if (!setcomp && !memchr(f, *s, setsize)) break; if (setcomp && memchr(f, *s, setsize)) break; if (do_cnv) *arg++ = *s++; } if (do_cnv) *arg = '\0', ++cnv; f = end; break; } case 'c': { char *arg = va_arg(args, char *); if (width <= 0) width = 1; while (width--) { if (!*s) return FAIL; if (do_cnv) *arg++ = *s++; } if (do_cnv) ++cnv; break; } case 'n': { if (size == 'h') *va_arg(args, short *) = (short)(s - str); else if (size == 'l') *va_arg(args, long *) = (long)(s - str); else *va_arg(args, int *) = (int)(s - str); break; } case '%': { if (*s++ != '%') return cnv; break; } default: return FAIL; } } else if (isspace((int)(unsigned int)*f)) { while (isspace((int)(unsigned int)f[1])) ++f; while (isspace((int)(unsigned int)*s)) ++s; } else { if (*s++ != *f) return cnv; } } return cnv; } #ifdef TEST #undef _ISOC9X_SOURCE #undef __USE_ISOC9X #include #include int test_sscanf(const char *str, const char *format, ...) { int rc; va_list args; va_start(args, format); rc = vsscanf(str, format, args); va_end(args); return rc; } int main(int ac, char **av) { int errors = 0; short si1, si2; int i1, i2; long li1, li2; float f1, f2; double d1, d2; long double ld1, ld2; void *p1, *p2; char b1[128], b2[128]; char c1[128], c2[128]; char s1[128], s2[128]; short sn1, sn2; int in1, in2; long ln1, ln2; unsigned short su1, su2; unsigned int u1, u2; unsigned long lu1, lu2; char str[512]; int rc1, rc2; if (ac == 2 && !strcmp(av[1], "help")) { printf("usage: %s [show]\n", *av); return EXIT_SUCCESS; } printf("Testing: %s\n", "vsscanf"); /* Test one of everything */ sprintf(str, " abc -12 37 101 3.4e-1 12.34 102.23 xyz %p def ghi jkl %% ", p1 = (void *)0xdeadbeef ); rc1 = sscanf(str, " abc %hd %d %ld %e %le %Le xyz %p %127[^abc ] %3c %127s %hn %n%% %ln", &si1, &i1, &li1, &f1, &d1, &ld1, &p1, b1, c1, s1, &sn1, &in1, &ln1 ); rc2 = test_sscanf(str, " abc %hd %d %ld %e %le %Le xyz %p %127[^abc ] %3c %127s %hn %n%% %ln", &si2, &i2, &li2, &f2, &d2, &ld2, &p2, b2, c2, s2, &sn2, &in2, &ln2 ); if (ac >= 2 && !strcmp(av[1], "show")) { printf("in: %s\n", str); printf("rc1=%d, rc2=%d\n", rc1, rc2); printf("out1: si1<%hd> i1<%d> li1<%ld> f1<%e> d1<%le> ld1<%Le> p1<%p> b1<%s> c1<%s> s1<%s> sn1<%hd> in1<%d> ln1<%ld>\n", si1, i1, li1, f1, d1, ld1, p1, b1, c1, s1, sn1, in1, ln1 ); printf("out2: si2<%hd> i2<%d> li2<%ld> f2<%e> d2<%le> ld2<%Le> p2<%p> b2<%s> c2<%s> s2<%s> sn2<%hd> in2<%d> ln2<%ld>\n", si2, i2, li2, f2, d2, ld2, p2, b2, c2, s2, sn2, in2, ln2 ); } if (rc1 != rc2) ++errors, printf("Test1: failed (returned %d, not %d)\n", rc2, rc1); if (si1 != si2) ++errors, printf("Test2: failed (%%hd scanned %hd, not %hd)\n", si2, si1); if (i1 != i2) ++errors, printf("Test3: failed (%%d scanned %d, not %d)\n", i2, i1); if (li1 != li2) ++errors, printf("Test4: failed (%%ld scanned %ld, not %ld)\n", li2, li1); if (fabs(f2 - 3.4e-1) / 3.4e-1 >= 4 * FLT_EPSILON) ++errors, printf("Test5: failed (%%e scanned %e, not %e)\n", f2, f1); if (fabs(d2 - 12.34) / 12.34 >= 4 * DBL_EPSILON) ++errors, printf("Test6: failed (%%le scanned %le, not %le)\n", d2, d1); if (fabs(ld2 - 102.23) / 102.23 >= 4 * LDBL_EPSILON) ++errors, printf("Test7: failed (%%Le scanned %Le, not %Le)\n", ld2, ld1); if (p1 != p2) ++errors, printf("Test8: failed (%%p scanned %p, not %p)\n", p2, p1); if (strcmp(b1, b2)) ++errors, printf("Test9: failed (%%[^abc ] scanned \"%s\", not \"%s\")\n", b2, b1); if (memcmp(c1, c2, 3)) ++errors, printf("Test10: failed (%%3c scanned \"%3.3s\", not \"%3.3s\")\n", c2, c1); if (strcmp(s1, s2)) ++errors, printf("Test11: failed (%%s scanned \"%s\", not \"%s\")\n", s2, s1); if (sn1 != sn2) ++errors, printf("Test12: failed (%%hn scanned %hd, not %hd)\n", sn2, sn1); if (in1 != in2) ++errors, printf("Test13: failed (%%n scanned %d, not %d)\n", in2, in1); if (ln1 != ln2) ++errors, printf("Test14: failed (%%ln scanned %ld, not %ld)\n", ln2, ln1); /* Test different numeric bases */ #define TEST_NUM(i, var, tst, str, format) \ rc1 = sscanf(str, format, &s##var##1, &var##1, &l##var##1); \ rc2 = test_sscanf(str, format, &s##var##2, &var##2, &l##var##2); \ if (rc1 != rc2) \ ++errors, printf("Test%d: failed (returned %d, not %d)\n", (i), rc2, rc1); \ if (s##var##1 != s##var##2) \ ++errors, printf("Test%d: failed (%%h%c scanned %hd, not %hd)\n", (i), tst, s##var##2, s##var##1); \ if (var##1 != var##2) \ ++errors, printf("Test%d: failed (%%%c scanned %d, not %d)\n", (i), tst, var##2, var##1); \ if (l##var##1 != l##var##2) \ ++errors, printf("Test%d: failed (%%l%c scanned %ld, not %ld)\n", (i), tst, l##var##2, l##var##1) #define TEST_STR(i, len, str, format) \ rc1 = sscanf(str, format, b1, c1, s1); \ rc2 = test_sscanf(str, format, b2, c2, s2); \ if (rc1 != rc2) \ ++errors, printf("Test%d: failed (returned %d, not %d)\n", (i), rc2, rc1); \ if (strcmp(b1, b2)) \ ++errors, printf("Test%d: failed (%%%d[ scanned \"%s\", not \"%s\")\n", (i), (len), b2, b1); \ if (memcmp(c1, c2, len)) \ ++errors, printf("Test%d: failed (%%%dc scanned \"%*.*s\", not \"%*.*s\")\n", (i), (len), (len), (len), c2, (len), (len), c1); \ if (strcmp(s1, s2)) \ ++errors, printf("Test%d: failed (%%%ds scanned \"%s\", not \"%s\")\n", (i), (len), s2, s1) TEST_NUM(15, i, 'i', "37 21 53", "%hi %i %li"); TEST_NUM(16, i, 'i', "037 021 053", "%hi %i %li"); TEST_NUM(17, i, 'i', "0x37 0x21 0x53", "%hi %i %li"); TEST_NUM(18, u, 'o', "037 021 053", "%ho %o %lo"); TEST_NUM(19, u, 'u', "37 21 53", "%hu %u %lu"); TEST_NUM(20, u, 'x', "0x37 0x21 0x53", "%hx %x %lx"); TEST_NUM(21, u, 'X', "0x37 0x21 0x53", "%hx %x %lx"); /* Test field width handling */ TEST_NUM(22, i, 'd', "123456789", "%3hd %2d %4ld"); TEST_NUM(23, i, 'i', "123456789", "%3hi %2i %4li"); TEST_NUM(24, i, 'i', "012340789", "%3hi %2i %4li"); TEST_NUM(25, i, 'x', "123456789", "%3hi %2i %4li"); TEST_NUM(26, u, 'o', "012340789", "%3ho %2o %4lo"); TEST_NUM(27, u, 'u', "123456789", "%3hu %2u %4lu"); TEST_NUM(28, u, 'x', "123456789", "%3hx %2x %4lx"); TEST_NUM(29, u, 'X', "123456789", "%3hx %2X %4lX"); TEST_STR(30, 1, "abcd", "%1[a]%c%1s"); TEST_STR(31, 5, "abc\001d e f g h i", "%5[][abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()_=+\\|{};':\",./<>? -]%5c%5s"); TEST_STR(32, 3, "abc\001d e f g h i", "%3[][abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()_=+\\|{};':\",./<>? -]%3c%3s"); TEST_STR(33, 7, "abc\001d e f g h i", "%7[][abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()_=+\\|{};':\",./<>? -]%7c%7s"); TEST_STR(34, 2, "abc\001d e f g h i", "%2[][abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()_=+\\|{};':\",./<>? -]%2c%2s"); /* Test error reporting */ #define TEST_ERR(i, str, format) \ rc1 = sscanf(str, format); \ rc2 = test_sscanf(str, format); \ if (rc1 != rc2) \ ++errors, printf("Test%d: failed (returned %d, not %d)\n", (i), rc2, rc1) #define TEST_ERR_ARG(i, str, format, var) \ rc1 = sscanf(str, format, &var##1); \ rc2 = test_sscanf(str, format, &var##1); \ if (rc1 != rc2) \ ++errors, printf("Test%d: failed (returned %d, not %d)\n", (i), rc2, rc1) TEST_ERR_ARG(35, "", "%d", i); TEST_ERR_ARG(36, "", "%i", i); TEST_ERR_ARG(37, "", "%o", u); TEST_ERR_ARG(38, "", "%u", u); TEST_ERR_ARG(39, "", "%x", u); TEST_ERR_ARG(40, "", "%X", u); TEST_ERR_ARG(41, "", "%p", p); TEST_ERR_ARG(42, "", "%e", f); TEST_ERR_ARG(43, "", "%E", f); TEST_ERR_ARG(44, "", "%f", f); TEST_ERR_ARG(45, "", "%g", f); TEST_ERR_ARG(46, "", "%G", f); TEST_ERR_ARG(47, "", "%[abc]", *b); /* This fails when comparing against glibc */ TEST_ERR_ARG(48, "", "%c", *c); TEST_ERR(49, "a", "%%"); TEST_ERR(50, "a", "b"); if (errors) printf("%d/50 tests failed (This system's sscanf(3) might be wrong))\n", errors); else printf("All tests passed\n"); return (errors == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } #endif /* vi:set ts=4 sw=4: */