--- /dev/null
+/* 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);
endif
libc_wrapper_sources += files(
+ 'printf.c',
'stdio.c',
'stdlib.c',
'string.c',
--- /dev/null
+/* 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;
+}
'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',
--- /dev/null
+/* 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);