mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 00:14:32 +09:00
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:
committed by
Yu Watanabe
parent
bf9bc5beb0
commit
3bc03c67f0
27
src/include/musl/printf.h
Normal file
27
src/include/musl/printf.h
Normal 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);
|
||||
@@ -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
263
src/libc/musl/printf.c
Normal 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;
|
||||
}
|
||||
@@ -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
124
src/test/test-printf.c
Normal 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);
|
||||
Reference in New Issue
Block a user