From: Emil Renner Berthing Date: Sat, 22 May 2021 18:26:24 +0000 (+0200) Subject: musl: add fallback parse_printf_format() implementation X-Git-Tag: v259-rc1~30^2~2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3bc03c67f0ea345e7fad49ed22d1e72bd833c1f9;p=thirdparty%2Fsystemd.git musl: add fallback parse_printf_format() implementation musl does not provide parse_printf_format(). Let's introduce a fallback method. Co-authored-by: Yu Watanabe --- diff --git a/src/include/musl/printf.h b/src/include/musl/printf.h new file mode 100644 index 00000000000..aa5382613a1 --- /dev/null +++ b/src/include/musl/printf.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Copyright 2014 Emil Renner Berthing */ +#pragma once + +#include + +enum { /* C type: */ + PA_INT, /* int */ + PA_CHAR, /* int, cast to char */ + PA_WCHAR, /* wide char */ + PA_STRING, /* const char *, a '\0'-terminated string */ + PA_WSTRING, /* const wchar_t *, wide character string */ + PA_POINTER, /* void * */ + PA_FLOAT, /* float */ + PA_DOUBLE, /* double */ + PA_LAST, +}; + +/* Flag bits that can be set in a type returned by `parse_printf_format'. */ +#define PA_FLAG_MASK 0xff00 +#define PA_FLAG_LONG_LONG (1 << 8) +#define PA_FLAG_LONG_DOUBLE PA_FLAG_LONG_LONG +#define PA_FLAG_LONG (1 << 9) +#define PA_FLAG_SHORT (1 << 10) +#define PA_FLAG_PTR (1 << 11) + +size_t parse_printf_format(const char *fmt, size_t n, int *types); diff --git a/src/libc/musl/meson.build b/src/libc/musl/meson.build index 54866a8b612..387f04dac41 100644 --- a/src/libc/musl/meson.build +++ b/src/libc/musl/meson.build @@ -5,6 +5,7 @@ if get_option('libc') != 'musl' endif libc_wrapper_sources += files( + 'printf.c', 'stdio.c', 'stdlib.c', 'string.c', diff --git a/src/libc/musl/printf.c b/src/libc/musl/printf.c new file mode 100644 index 00000000000..6be5733f39d --- /dev/null +++ b/src/libc/musl/printf.c @@ -0,0 +1,263 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Copyright 2014 Emil Renner Berthing */ + +#include +#include +#include +#include + +static const char* consume_nonarg(const char *fmt) { + do { + if (*fmt == '\0') + return fmt; + } while (*fmt++ != '%'); + return fmt; +} + +static const char* consume_num(const char *fmt) { + for (;*fmt >= '0' && *fmt <= '9'; fmt++) + /* do nothing */; + return fmt; +} + +static const char* consume_argn(const char *fmt, size_t *arg) { + const char *p = fmt; + size_t val = 0; + + if (*p < '1' || *p > '9') + return fmt; + do { + val = 10*val + (*p++ - '0'); + } while (*p >= '0' && *p <= '9'); + + if (*p != '$') + return fmt; + *arg = val; + return p+1; +} + +static const char* consume_flags(const char *fmt) { + for (;;) + switch (*fmt) { + case '#': + case '0': + case '-': + case ' ': + case '+': + case '\'': + case 'I': + fmt++; + continue; + default: + return fmt; + } +} + +enum state { + BARE, + LPRE, + LLPRE, + HPRE, + HHPRE, + BIGLPRE, + ZTPRE, + JPRE, + STOP, +}; + +enum type { + NONE, + PTR, + STR, + WSTR, + INT, + SHORT, + LONG, + LLONG, + IMAX, + SIZET, + CHAR, + WCHAR, + DBL, + LDBL, + NPTR, + _TYPE_MAX, +}; + +static const short pa_types[_TYPE_MAX] = { + [NONE] = PA_INT, + [PTR] = PA_POINTER, + [STR] = PA_STRING, + [WSTR] = PA_WSTRING, + [INT] = PA_INT, + [SHORT] = PA_INT | PA_FLAG_SHORT, + [LONG] = PA_INT | PA_FLAG_LONG, +#if ULLONG_MAX > ULONG_MAX + [LLONG] = PA_INT | PA_FLAG_LONG_LONG, +#else + [LLONG] = PA_INT | PA_FLAG_LONG, +#endif +#if UINTMAX_MAX > ULONG_MAX + [IMAX] = PA_INT | PA_FLAG_LONG_LONG, +#elif UINTMAX_MAX > UINT_MAX + [IMAX] = PA_INT | PA_FLAG_LONG, +#else + [IMAX] = PA_INT, +#endif +#if SIZE_MAX > ULONG_MAX + [SIZET] = PA_INT | PA_FLAG_LONG_LONG, +#elif SIZE_MAX > UINT_MAX + [SIZET] = PA_INT | PA_FLAG_LONG, +#else + [SIZET] = PA_INT, +#endif + [CHAR] = PA_CHAR, + [WCHAR] = PA_WCHAR, + [DBL] = PA_DOUBLE, + [LDBL] = PA_DOUBLE | PA_FLAG_LONG_DOUBLE, + [NPTR] = PA_FLAG_PTR, +}; + +#define S(x) [(x)-'A'] +#define E(x) (STOP + (x)) + +static const unsigned char states[]['z'-'A'+1] = { + { /* 0: bare types */ + S('d') = E(INT), S('i') = E(INT), + S('o') = E(INT), S('u') = E(INT), S('x') = E(INT), S('X') = E(INT), + S('e') = E(DBL), S('f') = E(DBL), S('g') = E(DBL), S('a') = E(DBL), + S('E') = E(DBL), S('F') = E(DBL), S('G') = E(DBL), S('A') = E(DBL), + S('c') = E(CHAR), S('C') = E(WCHAR), + S('s') = E(STR), S('S') = E(WSTR), S('p') = E(PTR), + S('n') = E(NPTR), + S('m') = E(NONE), + S('l') = LPRE, S('q') = LLPRE, S('h') = HPRE, S('L') = BIGLPRE, + S('z') = ZTPRE, S('Z') = ZTPRE, S('j') = JPRE, S('t') = ZTPRE, + }, + { /* 1: l-prefixed */ + S('d') = E(LONG), S('i') = E(LONG), + S('o') = E(LONG), S('u') = E(LONG), S('x') = E(LONG), S('X') = E(LONG), + S('e') = E(DBL), S('f') = E(DBL), S('g') = E(DBL), S('a') = E(DBL), + S('E') = E(DBL), S('F') = E(DBL), S('G') = E(DBL), S('A') = E(DBL), + S('c') = E(CHAR), S('s') = E(STR), + S('n') = E(NPTR), + S('l') = LLPRE, + }, + { /* 2: ll-prefixed */ + S('d') = E(LLONG), S('i') = E(LLONG), + S('o') = E(LLONG), S('u') = E(LLONG), S('x') = E(LLONG), S('X') = E(LLONG), + S('n') = E(NPTR), + }, + { /* 3: h-prefixed */ + S('d') = E(SHORT), S('i') = E(SHORT), + S('o') = E(SHORT), S('u') = E(SHORT), S('x') = E(SHORT), S('X') = E(SHORT), + S('n') = E(NPTR), + S('h') = HHPRE, + }, + { /* 4: hh-prefixed */ + S('d') = E(CHAR), S('i') = E(CHAR), + S('o') = E(CHAR), S('u') = E(CHAR), S('x') = E(CHAR), S('X') = E(CHAR), + S('n') = E(NPTR), + }, + { /* 5: L-prefixed */ + S('e') = E(LDBL), S('f') = E(LDBL), S('g') = E(LDBL), S('a') = E(LDBL), + S('E') = E(LDBL), S('F') = E(LDBL), S('G') = E(LDBL), S('A') = E(LDBL), + }, + { /* 6: z- or t-prefixed (assumed to be same size) */ + S('d') = E(SIZET), S('i') = E(SIZET), + S('o') = E(SIZET), S('u') = E(SIZET), S('x') = E(SIZET), S('X') = E(SIZET), + S('n') = E(NPTR), + }, + { /* 7: j-prefixed */ + S('d') = E(IMAX), S('i') = E(IMAX), + S('o') = E(IMAX), S('u') = E(IMAX), S('x') = E(IMAX), S('X') = E(IMAX), + S('n') = E(NPTR), + }, +}; + +size_t parse_printf_format(const char *fmt, size_t n, int *types) { + size_t i = 0; + size_t last = 0; + + memset(types, 0, n); + + for (;;) { + size_t arg; + + fmt = consume_nonarg(fmt); + if (*fmt == '\0') + break; + if (*fmt == '%') { + fmt++; + continue; + } + arg = 0; + fmt = consume_argn(fmt, &arg); + /* flags */ + fmt = consume_flags(fmt); + /* width */ + if (*fmt == '*') { + size_t warg = 0; + fmt = consume_argn(fmt+1, &warg); + if (warg == 0) + warg = ++i; + if (warg > last) + last = warg; + if (warg <= n && types[warg-1] == NONE) + types[warg-1] = INT; + } else + fmt = consume_num(fmt); + /* precision */ + if (*fmt == '.') { + fmt++; + if (*fmt == '*') { + size_t parg = 0; + fmt = consume_argn(fmt+1, &parg); + if (parg == 0) + parg = ++i; + if (parg > last) + last = parg; + if (parg <= n && types[parg-1] == NONE) + types[parg-1] = INT; + } else { + if (*fmt == '-') + fmt++; + fmt = consume_num(fmt); + } + } + /* length modifier and conversion specifier */ + unsigned state = BARE; + for (;;) { + unsigned char c = *fmt; + + if (c == '\0') + break; + + fmt++; + + if (c < 'A' || c > 'z') + break; + + state = states[state]S(c); + if (state == 0 || state >= STOP) + break; + } + + if (state <= STOP) /* %m or invalid format */ + continue; + + if (arg == 0) + arg = ++i; + if (arg > last) + last = arg; + if (arg <= n) + types[arg-1] = state - STOP; + } + + if (last > n) + last = n; + for (i = 0; i < last; i++) + types[i] = pa_types[types[i]]; + + return last; +} diff --git a/src/test/meson.build b/src/test/meson.build index 919d622ac66..d51911bd5bb 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -164,6 +164,7 @@ simple_tests += files( 'test-percent-util.c', 'test-pidref.c', 'test-pretty-print.c', + 'test-printf.c', 'test-prioq.c', 'test-proc-cmdline.c', 'test-procfs-util.c', diff --git a/src/test/test-printf.c b/src/test/test-printf.c new file mode 100644 index 00000000000..8a105c26bb0 --- /dev/null +++ b/src/test/test-printf.c @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "memory-util.h" +#include "strv.h" +#include "tests.h" + +static void test_parse_printf_format_one(const char *fmt, size_t m, const int *expected) { + log_debug("/* %s(%s) */", __func__, fmt); + + int types[128] = {}; + size_t n = parse_printf_format(fmt, ELEMENTSOF(types), types); + + for (size_t i = 0; i < MAX(n, m); i++) + if (i < MIN(n, m)) + log_debug("types[%zu]=%i, expected[%zu]=%i", i, types[i], i, expected[i]); + else if (i < n) + log_debug("types[%zu]=%i, expected[%zu]=n/a", i, types[i], i); + else + log_debug("types[%zu]=n/a, expected[%zu]=%i", i, i, expected[i]); + + ASSERT_EQ(memcmp_nn(types, n * sizeof(int), expected, m * sizeof(int)), 0); +} + +TEST(parse_printf_format) { + static struct { + const char *prefix; + int expected; + } integer_table[] = { + { "", PA_INT }, + { "hh", PA_CHAR }, + { "h", PA_INT | PA_FLAG_SHORT }, + { "l", PA_INT | PA_FLAG_LONG }, +#if ULLONG_MAX > ULONG_MAX + { "ll", PA_INT | PA_FLAG_LONG_LONG }, +#else + { "ll", PA_INT | PA_FLAG_LONG }, +#endif +#if UINTMAX_MAX > ULONG_MAX + { "j", PA_INT | PA_FLAG_LONG_LONG }, +#elif UINTMAX_MAX > UINT_MAX + { "j", PA_INT | PA_FLAG_LONG }, +#else + { "j", PA_INT }, +#endif +#if SIZE_MAX > ULONG_MAX + { "z", PA_INT | PA_FLAG_LONG_LONG }, + { "Z", PA_INT | PA_FLAG_LONG_LONG }, + { "t", PA_INT | PA_FLAG_LONG_LONG }, +#elif SIZE_MAX > UINT_MAX + { "z", PA_INT | PA_FLAG_LONG }, + { "Z", PA_INT | PA_FLAG_LONG }, + { "t", PA_INT | PA_FLAG_LONG }, +#else + { "z", PA_INT }, + { "Z", PA_INT }, + { "t", PA_INT }, +#endif + }, float_table[] = { + { "", PA_DOUBLE }, + { "L", PA_DOUBLE | PA_FLAG_LONG_DOUBLE }, + }; + + FOREACH_ELEMENT(i, integer_table) { + _cleanup_free_ char *fmt = NULL; + + FOREACH_STRING(s, "d", "i", "o", "u", "x", "X") { + ASSERT_NOT_NULL(fmt = strjoin("%", i->prefix, s)); + test_parse_printf_format_one(fmt, 1, &i->expected); + fmt = mfree(fmt); + } + + ASSERT_NOT_NULL(fmt = strjoin("%", i->prefix, "n")); + test_parse_printf_format_one(fmt, 1, (int[]){ PA_INT | PA_FLAG_PTR }); + + fmt = mfree(fmt); + + ASSERT_NOT_NULL(fmt = strjoin("%", i->prefix)); + test_parse_printf_format_one(fmt, 0, NULL); + } + + FOREACH_ELEMENT(i, float_table) { + _cleanup_free_ char *fmt = NULL; + + FOREACH_STRING(s, "e", "E", "f", "F", "g", "G", "a", "A") { + ASSERT_NOT_NULL(fmt = strjoin("%", i->prefix, s)); + test_parse_printf_format_one(fmt, 1, &i->expected); + fmt = mfree(fmt); + } + + ASSERT_NOT_NULL(fmt = strjoin("%", i->prefix)); + test_parse_printf_format_one(fmt, 0, NULL); + } + + test_parse_printf_format_one("%c", 1, (int[]) { PA_CHAR }); + test_parse_printf_format_one("%lc", 1, (int[]) { PA_CHAR }); + test_parse_printf_format_one("%C", 1, (int[]) { PA_WCHAR }); + + test_parse_printf_format_one("%s", 1, (int[]) { PA_STRING }); + test_parse_printf_format_one("%ls", 1, (int[]) { PA_STRING }); + test_parse_printf_format_one("%S", 1, (int[]) { PA_WSTRING }); + + test_parse_printf_format_one("%p", 1, (int[]) { PA_POINTER }); + + test_parse_printf_format_one("%m", 0, NULL); + test_parse_printf_format_one("%%", 0, NULL); + + test_parse_printf_format_one("asfhghejmlahpgakdmsalc", 0, NULL); + test_parse_printf_format_one( + "%d%i%o%u%x%X", 6, + (int[]) { PA_INT, PA_INT, PA_INT, PA_INT, PA_INT, PA_INT }); + test_parse_printf_format_one( + "%e%E%f%F%g%G%a%A", 8, + (int[]) { PA_DOUBLE, PA_DOUBLE, PA_DOUBLE, PA_DOUBLE, PA_DOUBLE, PA_DOUBLE, PA_DOUBLE, PA_DOUBLE }); + test_parse_printf_format_one( + "%c%s%C%S%p%n%m%%", 6, + (int[]) { PA_CHAR, PA_STRING, PA_WCHAR, PA_WSTRING, PA_POINTER, PA_INT | PA_FLAG_PTR }); + test_parse_printf_format_one( + "%03d%-05d%+i%hhu%hu%hx%lx", 7, + (int[]) { PA_INT, PA_INT, PA_INT, PA_CHAR, PA_INT | PA_FLAG_SHORT, PA_INT | PA_FLAG_SHORT, PA_INT | PA_FLAG_LONG }); +} + +DEFINE_TEST_MAIN(LOG_DEBUG);