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 <watanabe.yu+github@gmail.com>
This commit is contained in:
Emil Renner Berthing
2021-05-22 20:26:24 +02:00
committed by Yu Watanabe
parent bf9bc5beb0
commit 3bc03c67f0
5 changed files with 416 additions and 0 deletions

27
src/include/musl/printf.h Normal file
View File

@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/* Copyright 2014 Emil Renner Berthing <systemd@esmil.dk> */
#pragma once
#include <stddef.h>
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);

View File

@@ -5,6 +5,7 @@ if get_option('libc') != 'musl'
endif
libc_wrapper_sources += files(
'printf.c',
'stdio.c',
'stdlib.c',
'string.c',

263
src/libc/musl/printf.c Normal file
View File

@@ -0,0 +1,263 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/* Copyright 2014 Emil Renner Berthing <systemd@esmil.dk> */
#include <limits.h>
#include <printf.h>
#include <stdint.h>
#include <string.h>
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;
}

View File

@@ -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',

124
src/test/test-printf.c Normal file
View File

@@ -0,0 +1,124 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <printf.h>
#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);