]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
musl: add fallback parse_printf_format() implementation
authorEmil Renner Berthing <systemd@esmil.dk>
Sat, 22 May 2021 18:26:24 +0000 (20:26 +0200)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 17 Nov 2025 03:19:22 +0000 (12:19 +0900)
musl does not provide parse_printf_format(). Let's introduce a fallback
method.

Co-authored-by: Yu Watanabe <watanabe.yu+github@gmail.com>
src/include/musl/printf.h [new file with mode: 0644]
src/libc/musl/meson.build
src/libc/musl/printf.c [new file with mode: 0644]
src/test/meson.build
src/test/test-printf.c [new file with mode: 0644]

diff --git a/src/include/musl/printf.h b/src/include/musl/printf.h
new file mode 100644 (file)
index 0000000..aa53826
--- /dev/null
@@ -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);
index 54866a8b612766565879bf61513fb7b0b2d1286f..387f04dac410457699c4df6816f52e4f56df9db9 100644 (file)
@@ -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 (file)
index 0000000..6be5733
--- /dev/null
@@ -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;
+}
index 919d622ac660ba5429aa25fe8108c29c8bd67b6e..d51911bd5bb3cec2452bfe10b6475254d7643667 100644 (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',
diff --git a/src/test/test-printf.c b/src/test/test-printf.c
new file mode 100644 (file)
index 0000000..8a105c2
--- /dev/null
@@ -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);