summaryrefslogtreecommitdiffstats
path: root/lib/printf
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/printf/README9
-rw-r--r--lib/printf/glue.c294
-rw-r--r--lib/printf/printf-pos.c791
-rw-r--r--lib/printf/printfcommon.h256
-rw-r--r--lib/printf/printflocal.h105
-rw-r--r--lib/printf/vfprintf.c840
-rw-r--r--lib/printfrr.h315
7 files changed, 2610 insertions, 0 deletions
diff --git a/lib/printf/README b/lib/printf/README
new file mode 100644
index 0000000..46d51ce
--- /dev/null
+++ b/lib/printf/README
@@ -0,0 +1,9 @@
+This is the printf implementation from FreeBSD. The history of this code is:
+- imported on 2019-05-12, from SVN revision 347514
+- resynced on 2023-09-03, to pick up `%b` implementation
+
+Please don't reindent or otherwise mangle the files to make importing fixes
+easy (not that there are significant changes likely to happen...)
+
+The changes to this code are published under FreeBSD's license as listed in the
+file headers. If you change license, please make that as obvious as possible.
diff --git a/lib/printf/glue.c b/lib/printf/glue.c
new file mode 100644
index 0000000..f799378
--- /dev/null
+++ b/lib/printf/glue.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2019 David Lamparter, for NetDEF, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+#include <string.h>
+#include <wchar.h>
+
+#include "printfrr.h"
+#include "printflocal.h"
+
+ssize_t bprintfrr(struct fbuf *out, const char *fmt, ...)
+{
+ ssize_t ret;
+ va_list ap;
+
+ va_start(ap, fmt);
+ ret = vbprintfrr(out, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+ssize_t vsnprintfrr(char *out, size_t outsz, const char *fmt, va_list ap)
+{
+ struct fbuf fbb = { .buf = out, .pos = out, .len = outsz - 1, };
+ struct fbuf *fb = (out && outsz) ? &fbb : NULL;
+ ssize_t ret;
+
+ ret = vbprintfrr(fb, fmt, ap);
+ if (fb)
+ fb->pos[0] = '\0';
+ return ret;
+}
+
+ssize_t snprintfrr(char *out, size_t outsz, const char *fmt, ...)
+{
+ struct fbuf fbb = { .buf = out, .pos = out, .len = outsz - 1, };
+ struct fbuf *fb = (out && outsz) ? &fbb : NULL;
+ ssize_t ret;
+ va_list ap;
+
+ va_start(ap, fmt);
+ ret = vbprintfrr(fb, fmt, ap);
+ va_end(ap);
+ if (fb)
+ fb->pos[0] = '\0';
+ return ret;
+}
+
+ssize_t vcsnprintfrr(char *out, size_t outsz, const char *fmt, va_list ap)
+{
+ if (!out || !outsz)
+ return vbprintfrr(NULL, fmt, ap);
+
+ struct fbuf fbb = { .buf = out, .pos = out, .len = outsz - 1, };
+ ssize_t ret;
+ size_t pos;
+
+ pos = strnlen(out, outsz);
+ fbb.pos += pos;
+
+ ret = vbprintfrr(&fbb, fmt, ap);
+ fbb.pos[0] = '\0';
+ return ret >= 0 ? ret + (ssize_t)pos : ret;
+}
+
+ssize_t csnprintfrr(char *out, size_t outsz, const char *fmt, ...)
+{
+ ssize_t ret;
+ va_list ap;
+
+ va_start(ap, fmt);
+ ret = vcsnprintfrr(out, outsz, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+char *vasnprintfrr(struct memtype *mt, char *out, size_t outsz, const char *fmt,
+ va_list ap)
+{
+ struct fbuf fb = { .buf = out, .pos = out, .len = outsz - 1, };
+ ssize_t len;
+ va_list ap2;
+ char *ret = out;
+
+ va_copy(ap2, ap);
+ len = vbprintfrr(&fb, fmt, ap);
+ if (len < 0) {
+ va_end(ap2);
+ /* error = malformed format string => try something useful */
+ return qstrdup(mt, fmt);
+ }
+
+ if ((size_t)len >= outsz - 1) {
+ ret = qmalloc(mt, len + 1);
+ fb.buf = fb.pos = ret;
+ fb.len = len;
+
+ vbprintfrr(&fb, fmt, ap2);
+ }
+
+ va_end(ap2);
+ ret[len] = '\0';
+ return ret;
+}
+
+char *asnprintfrr(struct memtype *mt, char *out, size_t outsz, const char *fmt,
+ ...)
+{
+ va_list ap;
+ char *ret;
+
+ va_start(ap, fmt);
+ ret = vasnprintfrr(mt, out, outsz, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+char *vasprintfrr(struct memtype *mt, const char *fmt, va_list ap)
+{
+ char buf[256];
+ char *ret;
+
+ ret = vasnprintfrr(mt, buf, sizeof(buf), fmt, ap);
+
+ if (ret == buf)
+ ret = qstrdup(mt, ret);
+ return ret;
+}
+
+char *asprintfrr(struct memtype *mt, const char *fmt, ...)
+{
+ char buf[256];
+ va_list ap;
+ char *ret;
+
+ va_start(ap, fmt);
+ ret = vasnprintfrr(mt, buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ if (ret == buf)
+ ret = qstrdup(mt, ret);
+ return ret;
+}
+
+/* Q: WTF?
+ * A: since printf should be reasonably fast (think debugging logs), the idea
+ * here is to keep things close by each other in a cacheline. That's why
+ * ext_quick just has the first 2 characters of an extension, and we do a
+ * nice linear continuous sweep. Only if we find something, we go do more
+ * expensive things.
+ *
+ * Q: doesn't this need a mutex/lock?
+ * A: theoretically, yes, but that's quite expensive and I rather elide that
+ * necessity by putting down some usage rules. Just call this at startup
+ * while singlethreaded and all is fine. Ideally, just use constructors
+ * (and make sure dlopen() doesn't mess things up...)
+ */
+#define MAXEXT 64
+
+struct ext_quick {
+ char fmt[2];
+};
+
+static uint8_t ext_offsets[26] __attribute__((aligned(32)));
+static struct ext_quick entries[MAXEXT] __attribute__((aligned(64)));
+static const struct printfrr_ext *exts[MAXEXT] __attribute__((aligned(64)));
+
+void printfrr_ext_reg(const struct printfrr_ext *ext)
+{
+ uint8_t o;
+ ptrdiff_t i;
+
+ if (!printfrr_ext_char(ext->match[0]))
+ return;
+
+ o = ext->match[0] - 'A';
+ for (i = ext_offsets[o];
+ i < MAXEXT && entries[i].fmt[0] &&
+ memcmp(entries[i].fmt, ext->match, 2) < 0;
+ i++)
+ ;
+ if (i == MAXEXT)
+ return;
+ for (o++; o <= 'Z' - 'A'; o++)
+ ext_offsets[o]++;
+
+ memmove(entries + i + 1, entries + i,
+ (MAXEXT - i - 1) * sizeof(entries[0]));
+ memmove(exts + i + 1, exts + i,
+ (MAXEXT - i - 1) * sizeof(exts[0]));
+
+ memcpy(entries[i].fmt, ext->match, 2);
+ exts[i] = ext;
+}
+
+ssize_t printfrr_extp(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ const char *fmt = ea->fmt;
+ const struct printfrr_ext *ext;
+ size_t i;
+
+ for (i = ext_offsets[fmt[0] - 'A']; i < MAXEXT; i++) {
+ if (!entries[i].fmt[0] || entries[i].fmt[0] > fmt[0])
+ return -1;
+ if (entries[i].fmt[1] && entries[i].fmt[1] != fmt[1])
+ continue;
+ ext = exts[i];
+ if (!ext->print_ptr)
+ continue;
+ if (strncmp(ext->match, fmt, strlen(ext->match)))
+ continue;
+ ea->fmt += strlen(ext->match);
+ return ext->print_ptr(buf, ea, ptr);
+ }
+ return -1;
+}
+
+ssize_t printfrr_exti(struct fbuf *buf, struct printfrr_eargs *ea,
+ uintmax_t num)
+{
+ const char *fmt = ea->fmt;
+ const struct printfrr_ext *ext;
+ size_t i;
+
+ for (i = ext_offsets[fmt[0] - 'A']; i < MAXEXT; i++) {
+ if (!entries[i].fmt[0] || entries[i].fmt[0] > fmt[0])
+ return -1;
+ if (entries[i].fmt[1] && entries[i].fmt[1] != fmt[1])
+ continue;
+ ext = exts[i];
+ if (!ext->print_int)
+ continue;
+ if (strncmp(ext->match, fmt, strlen(ext->match)))
+ continue;
+ ea->fmt += strlen(ext->match);
+ return ext->print_int(buf, ea, num);
+ }
+ return -1;
+}
+
+printfrr_ext_autoreg_p("FB", printfrr_fb);
+static ssize_t printfrr_fb(struct fbuf *out, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ const struct fbuf *in = ptr;
+ ptrdiff_t copy_len;
+
+ if (!in)
+ return bputs(out, "NULL");
+
+ if (out) {
+ copy_len = MIN(in->pos - in->buf,
+ out->buf + out->len - out->pos);
+ if (copy_len > 0) {
+ memcpy(out->pos, in->buf, copy_len);
+ out->pos += copy_len;
+ }
+ }
+
+ return in->pos - in->buf;
+}
+
+printfrr_ext_autoreg_p("VA", printfrr_va);
+static ssize_t printfrr_va(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ const struct va_format *vaf = ptr;
+ va_list ap;
+ ssize_t s;
+
+ if (!vaf || !vaf->fmt || !vaf->va)
+ return bputs(buf, "NULL");
+
+ /* make sure we don't alter the data passed in - especially since
+ * bprintfrr (and thus this) might be called on the same format twice,
+ * when allocating a larger buffer in asnprintfrr()
+ */
+ va_copy(ap, *vaf->va);
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ /* can't format check this */
+ s = vbprintfrr(buf, vaf->fmt, ap);
+#pragma GCC diagnostic pop
+ va_end(ap);
+
+ return s;
+}
diff --git a/lib/printf/printf-pos.c b/lib/printf/printf-pos.c
new file mode 100644
index 0000000..ac775be
--- /dev/null
+++ b/lib/printf/printf-pos.c
@@ -0,0 +1,791 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_SYS_CDEFS_H
+#include <sys/cdefs.h>
+#endif
+
+/*
+ * This is the code responsible for handling positional arguments
+ * (%m$ and %m$.n$) for vfprintf() and vfwprintf().
+ */
+
+#include <sys/types.h>
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <wchar.h>
+
+#include "printflocal.h"
+
+#ifdef NL_ARGMAX
+#define MAX_POSARG NL_ARGMAX
+#else
+#define MAX_POSARG 65536
+#endif
+
+/*
+ * Type ids for argument type table.
+ */
+enum typeid {
+ T_UNUSED, TP_SHORT, T_INT, T_U_INT, TP_INT,
+ T_LONG, T_U_LONG, TP_LONG, T_LLONG, T_U_LLONG, TP_LLONG,
+ T_PTRDIFFT, TP_PTRDIFFT, T_SSIZET, T_SIZET, TP_SSIZET,
+ T_INT64T, T_UINT64T, T_INTMAXT, T_UINTMAXT, TP_INTMAXT, TP_VOID,
+ TP_CHAR, TP_SCHAR, T_DOUBLE, T_LONG_DOUBLE, T_WINT, TP_WCHAR
+};
+
+/* An expandable array of types. */
+struct typetable {
+ enum typeid *table; /* table of types */
+ enum typeid stattable[STATIC_ARG_TBL_SIZE];
+ u_int tablesize; /* current size of type table */
+ u_int tablemax; /* largest used index in table */
+ u_int nextarg; /* 1-based argument index */
+};
+
+static int __grow_type_table(struct typetable *);
+static void build_arg_table (struct typetable *, va_list, union arg **);
+
+/*
+ * Initialize a struct typetable.
+ */
+static inline void
+inittypes(struct typetable *types)
+{
+ u_int n;
+
+ types->table = types->stattable;
+ types->tablesize = STATIC_ARG_TBL_SIZE;
+ types->tablemax = 0;
+ types->nextarg = 1;
+ for (n = 0; n < STATIC_ARG_TBL_SIZE; n++)
+ types->table[n] = T_UNUSED;
+}
+
+/*
+ * struct typetable destructor.
+ */
+static inline void
+freetypes(struct typetable *types)
+{
+
+ if (types->table != types->stattable)
+ free (types->table);
+}
+
+/*
+ * Ensure that there is space to add a new argument type to the type table.
+ * Expand the table if necessary. Returns 0 on success.
+ */
+static inline int
+_ensurespace(struct typetable *types)
+{
+
+ if (types->nextarg >= types->tablesize) {
+ if (__grow_type_table(types))
+ return -1;
+ }
+ if (types->nextarg > types->tablemax)
+ types->tablemax = types->nextarg;
+ return 0;
+}
+
+/*
+ * Add an argument type to the table, expanding if necessary.
+ * Returns 0 on success.
+ */
+static inline int
+addtype(struct typetable *types, enum typeid type)
+{
+
+ if (_ensurespace(types))
+ return -1;
+ types->table[types->nextarg++] = type;
+ return 0;
+}
+
+static inline int
+addsarg(struct typetable *types, int flags)
+{
+
+ if (_ensurespace(types))
+ return -1;
+ if (flags & LONGDBL)
+ types->table[types->nextarg++] = T_INT64T;
+ else if (flags & INTMAXT)
+ types->table[types->nextarg++] = T_INTMAXT;
+ else if (flags & SIZET)
+ types->table[types->nextarg++] = T_SSIZET;
+ else if (flags & PTRDIFFT)
+ types->table[types->nextarg++] = T_PTRDIFFT;
+ else if (flags & LLONGINT)
+ types->table[types->nextarg++] = T_LLONG;
+ else if (flags & LONGINT)
+ types->table[types->nextarg++] = T_LONG;
+ else
+ types->table[types->nextarg++] = T_INT;
+ return 0;
+}
+
+static inline int
+adduarg(struct typetable *types, int flags)
+{
+
+ if (_ensurespace(types))
+ return -1;
+ if (flags & LONGDBL)
+ types->table[types->nextarg++] = T_UINT64T;
+ else if (flags & INTMAXT)
+ types->table[types->nextarg++] = T_UINTMAXT;
+ else if (flags & SIZET)
+ types->table[types->nextarg++] = T_SIZET;
+ else if (flags & PTRDIFFT)
+ types->table[types->nextarg++] = T_SIZET;
+ else if (flags & LLONGINT)
+ types->table[types->nextarg++] = T_U_LLONG;
+ else if (flags & LONGINT)
+ types->table[types->nextarg++] = T_U_LONG;
+ else
+ types->table[types->nextarg++] = T_U_INT;
+ return 0;
+}
+
+/*
+ * Add * arguments to the type array.
+ */
+static inline int
+addaster(struct typetable *types, char **fmtp)
+{
+ char *cp;
+ u_int n2;
+
+ n2 = 0;
+ cp = *fmtp;
+ while (is_digit(*cp)) {
+ n2 = 10 * n2 + to_digit(*cp);
+ cp++;
+ }
+ if (*cp == '$') {
+ u_int hold = types->nextarg;
+ types->nextarg = n2;
+ if (addtype(types, T_INT))
+ return -1;
+ types->nextarg = hold;
+ *fmtp = ++cp;
+ } else {
+ if (addtype(types, T_INT))
+ return -1;
+ }
+ return 0;
+}
+
+#ifdef WCHAR_SUPPORT
+static inline int
+addwaster(struct typetable *types, wchar_t **fmtp)
+{
+ wchar_t *cp;
+ u_int n2;
+
+ n2 = 0;
+ cp = *fmtp;
+ while (is_digit(*cp)) {
+ n2 = 10 * n2 + to_digit(*cp);
+ cp++;
+ }
+ if (*cp == '$') {
+ u_int hold = types->nextarg;
+ types->nextarg = n2;
+ if (addtype(types, T_INT))
+ return -1;
+ types->nextarg = hold;
+ *fmtp = ++cp;
+ } else {
+ if (addtype(types, T_INT))
+ return -1;
+ }
+ return 0;
+}
+#endif /* WCHAR_SUPPORT */
+
+/*
+ * Find all arguments when a positional parameter is encountered. Returns a
+ * table, indexed by argument number, of pointers to each arguments. The
+ * initial argument table should be an array of STATIC_ARG_TBL_SIZE entries.
+ * It will be replaces with a malloc-ed one if it overflows.
+ * Returns 0 on success. On failure, returns nonzero and sets errno.
+ */
+int
+_frr_find_arguments (const char *fmt0, va_list ap, union arg **argtable)
+{
+ char *fmt; /* format string */
+ int ch; /* character from fmt */
+ u_int n; /* handy integer (short term usage) */
+ int error;
+ int flags; /* flags as above */
+ struct typetable types; /* table of types */
+
+ fmt = (char *)fmt0;
+ inittypes(&types);
+ error = 0;
+
+ /*
+ * Scan the format for conversions (`%' character).
+ */
+ for (;;) {
+ while ((ch = *fmt) != '\0' && ch != '%')
+ fmt++;
+ if (ch == '\0')
+ goto done;
+ fmt++; /* skip over '%' */
+
+ flags = 0;
+
+rflag: ch = *fmt++;
+reswitch: switch (ch) {
+ case ' ':
+ case '#':
+ goto rflag;
+ case '*':
+ if ((error = addaster(&types, &fmt)))
+ goto error;
+ goto rflag;
+ case '-':
+ case '+':
+ case '\'':
+ goto rflag;
+ case '.':
+ if ((ch = *fmt++) == '*') {
+ if ((error = addaster(&types, &fmt)))
+ goto error;
+ goto rflag;
+ }
+ while (is_digit(ch)) {
+ ch = *fmt++;
+ }
+ goto reswitch;
+ case '0':
+ goto rflag;
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ n = 0;
+ do {
+ n = 10 * n + to_digit(ch);
+ /* Detect overflow */
+ if (n > MAX_POSARG) {
+ error = -1;
+ goto error;
+ }
+ ch = *fmt++;
+ } while (is_digit(ch));
+ if (ch == '$') {
+ types.nextarg = n;
+ goto rflag;
+ }
+ goto reswitch;
+ case 'L':
+ flags |= LONGDBL;
+ goto rflag;
+ case 'h':
+ if (flags & SHORTINT) {
+ flags &= ~SHORTINT;
+ flags |= CHARINT;
+ } else
+ flags |= SHORTINT;
+ goto rflag;
+ case 'j':
+ flags |= INTMAXT;
+ goto rflag;
+ case 'l':
+ if (flags & LONGINT) {
+ flags &= ~LONGINT;
+ flags |= LLONGINT;
+ } else
+ flags |= LONGINT;
+ goto rflag;
+ case 'q':
+ flags |= LLONGINT; /* not necessarily */
+ goto rflag;
+ case 't':
+ flags |= PTRDIFFT;
+ goto rflag;
+ case 'z':
+ flags |= SIZET;
+ goto rflag;
+ case 'C':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'c':
+ error = addtype(&types,
+ (flags & LONGINT) ? T_WINT : T_INT);
+ if (error)
+ goto error;
+ break;
+ case 'D':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'd':
+ case 'i':
+ if ((error = addsarg(&types, flags)))
+ goto error;
+ break;
+#ifndef NO_FLOATING_POINT
+ case 'a':
+ case 'A':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'g':
+ case 'G':
+ error = addtype(&types,
+ (flags & LONGDBL) ? T_LONG_DOUBLE : T_DOUBLE);
+ if (error)
+ goto error;
+ break;
+#endif /* !NO_FLOATING_POINT */
+#ifdef DANGEROUS_PERCENT_N
+ case 'n':
+ if (flags & INTMAXT)
+ error = addtype(&types, TP_INTMAXT);
+ else if (flags & PTRDIFFT)
+ error = addtype(&types, TP_PTRDIFFT);
+ else if (flags & SIZET)
+ error = addtype(&types, TP_SSIZET);
+ else if (flags & LLONGINT)
+ error = addtype(&types, TP_LLONG);
+ else if (flags & LONGINT)
+ error = addtype(&types, TP_LONG);
+ else if (flags & SHORTINT)
+ error = addtype(&types, TP_SHORT);
+ else if (flags & CHARINT)
+ error = addtype(&types, TP_SCHAR);
+ else
+ error = addtype(&types, TP_INT);
+ if (error)
+ goto error;
+ continue; /* no output */
+#endif
+ case 'O':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'o':
+ if ((error = adduarg(&types, flags)))
+ goto error;
+ break;
+ case 'p':
+ if ((error = addtype(&types, TP_VOID)))
+ goto error;
+ break;
+ case 'S':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 's':
+ error = addtype(&types,
+ (flags & LONGINT) ? TP_WCHAR : TP_CHAR);
+ if (error)
+ goto error;
+ break;
+ case 'U':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'u':
+ case 'X':
+ case 'x':
+ if ((error = adduarg(&types, flags)))
+ goto error;
+ break;
+ default: /* "%?" prints ?, unless ? is NUL */
+ if (ch == '\0')
+ goto done;
+ break;
+ }
+ }
+done:
+ build_arg_table(&types, ap, argtable);
+error:
+ freetypes(&types);
+ return (error || *argtable == NULL);
+}
+
+#ifdef WCHAR_SUPPORT
+/* wchar version of __find_arguments. */
+int
+_frr_find_warguments (const wchar_t *fmt0, va_list ap, union arg **argtable)
+{
+ wchar_t *fmt; /* format string */
+ wchar_t ch; /* character from fmt */
+ u_int n; /* handy integer (short term usage) */
+ int error;
+ int flags; /* flags as above */
+ struct typetable types; /* table of types */
+
+ fmt = (wchar_t *)fmt0;
+ inittypes(&types);
+ error = 0;
+
+ /*
+ * Scan the format for conversions (`%' character).
+ */
+ for (;;) {
+ while ((ch = *fmt) != '\0' && ch != '%')
+ fmt++;
+ if (ch == '\0')
+ goto done;
+ fmt++; /* skip over '%' */
+
+ flags = 0;
+
+rflag: ch = *fmt++;
+reswitch: switch (ch) {
+ case ' ':
+ case '#':
+ goto rflag;
+ case '*':
+ if ((error = addwaster(&types, &fmt)))
+ goto error;
+ goto rflag;
+ case '-':
+ case '+':
+ case '\'':
+ goto rflag;
+ case '.':
+ if ((ch = *fmt++) == '*') {
+ if ((error = addwaster(&types, &fmt)))
+ goto error;
+ goto rflag;
+ }
+ while (is_digit(ch)) {
+ ch = *fmt++;
+ }
+ goto reswitch;
+ case '0':
+ goto rflag;
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ n = 0;
+ do {
+ n = 10 * n + to_digit(ch);
+ /* Detect overflow */
+ if (n > MAX_POSARG) {
+ error = -1;
+ goto error;
+ }
+ ch = *fmt++;
+ } while (is_digit(ch));
+ if (ch == '$') {
+ types.nextarg = n;
+ goto rflag;
+ }
+ goto reswitch;
+ case 'L':
+ flags |= LONGDBL;
+ goto rflag;
+ case 'h':
+ if (flags & SHORTINT) {
+ flags &= ~SHORTINT;
+ flags |= CHARINT;
+ } else
+ flags |= SHORTINT;
+ goto rflag;
+ case 'j':
+ flags |= INTMAXT;
+ goto rflag;
+ case 'l':
+ if (flags & LONGINT) {
+ flags &= ~LONGINT;
+ flags |= LLONGINT;
+ } else
+ flags |= LONGINT;
+ goto rflag;
+ case 'q':
+ flags |= LLONGINT; /* not necessarily */
+ goto rflag;
+ case 't':
+ flags |= PTRDIFFT;
+ goto rflag;
+ case 'z':
+ flags |= SIZET;
+ goto rflag;
+ case 'C':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'c':
+ error = addtype(&types,
+ (flags & LONGINT) ? T_WINT : T_INT);
+ if (error)
+ goto error;
+ break;
+ case 'D':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'd':
+ case 'i':
+ if ((error = addsarg(&types, flags)))
+ goto error;
+ break;
+#ifndef NO_FLOATING_POINT
+ case 'a':
+ case 'A':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'g':
+ case 'G':
+ error = addtype(&types,
+ (flags & LONGDBL) ? T_LONG_DOUBLE : T_DOUBLE);
+ if (error)
+ goto error;
+ break;
+#endif /* !NO_FLOATING_POINT */
+#ifdef DANGEROUS_PERCENT_N
+ case 'n':
+ if (flags & INTMAXT)
+ error = addtype(&types, TP_INTMAXT);
+ else if (flags & PTRDIFFT)
+ error = addtype(&types, TP_PTRDIFFT);
+ else if (flags & SIZET)
+ error = addtype(&types, TP_SSIZET);
+ else if (flags & LLONGINT)
+ error = addtype(&types, TP_LLONG);
+ else if (flags & LONGINT)
+ error = addtype(&types, TP_LONG);
+ else if (flags & SHORTINT)
+ error = addtype(&types, TP_SHORT);
+ else if (flags & CHARINT)
+ error = addtype(&types, TP_SCHAR);
+ else
+ error = addtype(&types, TP_INT);
+ if (error)
+ goto error;
+ continue; /* no output */
+#endif
+ case 'O':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'o':
+ if ((error = adduarg(&types, flags)))
+ goto error;
+ break;
+ case 'p':
+ if ((error = addtype(&types, TP_VOID)))
+ goto error;
+ break;
+ case 'S':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 's':
+ error = addtype(&types,
+ (flags & LONGINT) ? TP_WCHAR : TP_CHAR);
+ if (error)
+ goto error;
+ break;
+ case 'U':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'u':
+ case 'X':
+ case 'x':
+ if ((error = adduarg(&types, flags)))
+ goto error;
+ break;
+ default: /* "%?" prints ?, unless ? is NUL */
+ if (ch == '\0')
+ goto done;
+ break;
+ }
+ }
+done:
+ build_arg_table(&types, ap, argtable);
+error:
+ freetypes(&types);
+ return (error || *argtable == NULL);
+}
+#endif /* WCHAR_SUPPORT */
+
+/*
+ * Increase the size of the type table. Returns 0 on success.
+ */
+static int
+__grow_type_table(struct typetable *types)
+{
+ enum typeid *const oldtable = types->table;
+ const int oldsize = types->tablesize;
+ enum typeid *newtable;
+ u_int n, newsize;
+
+ /* Detect overflow */
+ if (types->nextarg > MAX_POSARG)
+ return -1;
+
+ newsize = oldsize * 2;
+ if (newsize < types->nextarg + 1)
+ newsize = types->nextarg + 1;
+ if (oldsize == STATIC_ARG_TBL_SIZE) {
+ if ((newtable = malloc(newsize * sizeof(enum typeid))) == NULL)
+ return -1;
+ bcopy(oldtable, newtable, oldsize * sizeof(enum typeid));
+ } else {
+ newtable = realloc(oldtable, newsize * sizeof(enum typeid));
+ if (newtable == NULL)
+ return -1;
+ }
+ for (n = oldsize; n < newsize; n++)
+ newtable[n] = T_UNUSED;
+
+ types->table = newtable;
+ types->tablesize = newsize;
+
+ return 0;
+}
+
+/*
+ * Build the argument table from the completed type table.
+ * On malloc failure, *argtable is set to NULL.
+ */
+static void
+build_arg_table(struct typetable *types, va_list ap, union arg **argtable)
+{
+ u_int n;
+
+ if (types->tablemax >= STATIC_ARG_TBL_SIZE) {
+ *argtable = (union arg *)
+ malloc (sizeof(union arg) * (types->tablemax + 1));
+ if (*argtable == NULL)
+ return;
+ }
+
+ (*argtable) [0].intarg = 0;
+ for (n = 1; n <= types->tablemax; n++) {
+ switch (types->table[n]) {
+ case T_UNUSED: /* whoops! */
+ (*argtable) [n].intarg = va_arg (ap, int);
+ break;
+ case TP_SCHAR:
+ (*argtable) [n].pschararg = va_arg (ap, signed char *);
+ break;
+ case TP_SHORT:
+ (*argtable) [n].pshortarg = va_arg (ap, short *);
+ break;
+ case T_INT:
+ (*argtable) [n].intarg = va_arg (ap, int);
+ break;
+ case T_U_INT:
+ (*argtable) [n].uintarg = va_arg (ap, unsigned int);
+ break;
+ case TP_INT:
+ (*argtable) [n].pintarg = va_arg (ap, int *);
+ break;
+ case T_LONG:
+ (*argtable) [n].longarg = va_arg (ap, long);
+ break;
+ case T_U_LONG:
+ (*argtable) [n].ulongarg = va_arg (ap, unsigned long);
+ break;
+ case TP_LONG:
+ (*argtable) [n].plongarg = va_arg (ap, long *);
+ break;
+ case T_LLONG:
+ (*argtable) [n].longlongarg = va_arg (ap, long long);
+ break;
+ case T_U_LLONG:
+ (*argtable) [n].ulonglongarg = va_arg (ap, unsigned long long);
+ break;
+ case TP_LLONG:
+ (*argtable) [n].plonglongarg = va_arg (ap, long long *);
+ break;
+ case T_PTRDIFFT:
+ (*argtable) [n].ptrdiffarg = va_arg (ap, ptrdiff_t);
+ break;
+ case TP_PTRDIFFT:
+ (*argtable) [n].pptrdiffarg = va_arg (ap, ptrdiff_t *);
+ break;
+ case T_SIZET:
+ (*argtable) [n].sizearg = va_arg (ap, size_t);
+ break;
+ case T_SSIZET:
+ (*argtable) [n].sizearg = va_arg (ap, ssize_t);
+ break;
+ case TP_SSIZET:
+ (*argtable) [n].pssizearg = va_arg (ap, ssize_t *);
+ break;
+ case T_INTMAXT:
+ (*argtable) [n].intmaxarg = va_arg (ap, intmax_t);
+ break;
+ case T_UINTMAXT:
+ (*argtable) [n].uintmaxarg = va_arg (ap, uintmax_t);
+ break;
+ case TP_INTMAXT:
+ (*argtable) [n].pintmaxarg = va_arg (ap, intmax_t *);
+ break;
+ case T_INT64T:
+ (*argtable) [n].intmaxarg = va_arg (ap, int64_t);
+ break;
+ case T_UINT64T:
+ (*argtable) [n].uintmaxarg = va_arg (ap, uint64_t);
+ break;
+ case T_DOUBLE:
+#ifndef NO_FLOATING_POINT
+ (*argtable) [n].doublearg = va_arg (ap, double);
+#endif
+ break;
+ case T_LONG_DOUBLE:
+#ifndef NO_FLOATING_POINT
+ (*argtable) [n].longdoublearg = va_arg (ap, long double);
+#endif
+ break;
+ case TP_CHAR:
+ (*argtable) [n].pchararg = va_arg (ap, char *);
+ break;
+ case TP_VOID:
+ (*argtable) [n].pvoidarg = va_arg (ap, void *);
+ break;
+ case T_WINT:
+ (*argtable) [n].wintarg = va_arg (ap, wint_t);
+ break;
+ case TP_WCHAR:
+ (*argtable) [n].pwchararg = va_arg (ap, wchar_t *);
+ break;
+ }
+ }
+}
diff --git a/lib/printf/printfcommon.h b/lib/printf/printfcommon.h
new file mode 100644
index 0000000..f777be8
--- /dev/null
+++ b/lib/printf/printfcommon.h
@@ -0,0 +1,256 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * Copyright (c) 2011 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by David Chisnall
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file defines common routines used by both printf and wprintf.
+ * You must define CHAR to either char or wchar_t prior to including this.
+ */
+
+
+static CHAR *__ujtoa(uintmax_t, CHAR *, int, int, const char *);
+static CHAR *__ultoa(u_long, CHAR *, int, int, const char *);
+
+#define NIOV 8
+struct io_state {
+ struct fbuf *cb;
+ size_t avail;
+};
+
+static inline void
+io_init(struct io_state *iop, struct fbuf *cb)
+{
+ iop->cb = cb;
+ iop->avail = cb ? cb->len - (cb->pos - cb->buf) : 0;
+}
+
+/*
+ * WARNING: The buffer passed to io_print() is not copied immediately; it must
+ * remain valid until io_flush() is called.
+ */
+static inline int
+io_print(struct io_state *iop, const CHAR * __restrict ptr, size_t len)
+{
+ size_t copylen = len;
+
+ if (!iop->cb || !len)
+ return 0;
+ if (iop->avail < copylen)
+ copylen = iop->avail;
+
+ memcpy(iop->cb->pos, ptr, copylen);
+ iop->avail -= copylen;
+ iop->cb->pos += copylen;
+ return 0;
+}
+
+/*
+ * Choose PADSIZE to trade efficiency vs. size. If larger printf
+ * fields occur frequently, increase PADSIZE and make the initialisers
+ * below longer.
+ */
+#define PADSIZE 16 /* pad chunk size */
+static const CHAR blanks[PADSIZE] =
+{' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
+static const CHAR zeroes[PADSIZE] =
+{'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
+
+/*
+ * Pad with blanks or zeroes. 'with' should point to either the blanks array
+ * or the zeroes array.
+ */
+static inline int
+io_pad(struct io_state *iop, int howmany, const CHAR * __restrict with)
+{
+ int n;
+
+ while (howmany > 0) {
+ n = (howmany >= PADSIZE) ? PADSIZE : howmany;
+ if (io_print(iop, with, n))
+ return (-1);
+ howmany -= n;
+ }
+ return (0);
+}
+
+/*
+ * Print exactly len characters of the string spanning p to ep, truncating
+ * or padding with 'with' as necessary.
+ */
+static inline int
+io_printandpad(struct io_state *iop, const CHAR *p, const CHAR *ep,
+ int len, const CHAR * __restrict with)
+{
+ int p_len;
+
+ p_len = ep - p;
+ if (p_len > len)
+ p_len = len;
+ if (p_len > 0) {
+ if (io_print(iop, p, p_len))
+ return (-1);
+ } else {
+ p_len = 0;
+ }
+ return (io_pad(iop, len - p_len, with));
+}
+
+/*
+ * Convert an unsigned long to ASCII for printf purposes, returning
+ * a pointer to the first character of the string representation.
+ * Octal numbers can be forced to have a leading zero; hex numbers
+ * use the given digits.
+ */
+static CHAR *
+__ultoa(u_long val, CHAR *endp, int base, int octzero, const char *xdigs)
+{
+ CHAR *cp = endp;
+ long sval;
+
+ /*
+ * Handle the three cases separately, in the hope of getting
+ * better/faster code.
+ */
+ switch (base) {
+ case 10:
+ if (val < 10) { /* many numbers are 1 digit */
+ *--cp = to_char(val);
+ return (cp);
+ }
+ /*
+ * On many machines, unsigned arithmetic is harder than
+ * signed arithmetic, so we do at most one unsigned mod and
+ * divide; this is sufficient to reduce the range of
+ * the incoming value to where signed arithmetic works.
+ */
+ if (val > LONG_MAX) {
+ *--cp = to_char(val % 10);
+ sval = val / 10;
+ } else
+ sval = val;
+ do {
+ *--cp = to_char(sval % 10);
+ sval /= 10;
+ } while (sval != 0);
+ break;
+
+ case 2:
+ do {
+ *--cp = to_char(val & 1);
+ val >>= 1;
+ } while (val);
+ break;
+
+ case 8:
+ do {
+ *--cp = to_char(val & 7);
+ val >>= 3;
+ } while (val);
+ if (octzero && *cp != '0')
+ *--cp = '0';
+ break;
+
+ case 16:
+ do {
+ *--cp = xdigs[val & 15];
+ val >>= 4;
+ } while (val);
+ break;
+
+ default: /* oops */
+ abort();
+ }
+ return (cp);
+}
+
+/* Identical to __ultoa, but for intmax_t. */
+static CHAR *
+__ujtoa(uintmax_t val, CHAR *endp, int base, int octzero, const char *xdigs)
+{
+ CHAR *cp = endp;
+ intmax_t sval;
+
+ /* quick test for small values; __ultoa is typically much faster */
+ /* (perhaps instead we should run until small, then call __ultoa?) */
+ if (val <= ULONG_MAX)
+ return (__ultoa((u_long)val, endp, base, octzero, xdigs));
+ switch (base) {
+ case 10:
+ if (val < 10) {
+ *--cp = to_char(val % 10);
+ return (cp);
+ }
+ if (val > INTMAX_MAX) {
+ *--cp = to_char(val % 10);
+ sval = val / 10;
+ } else
+ sval = val;
+ do {
+ *--cp = to_char(sval % 10);
+ sval /= 10;
+ } while (sval != 0);
+ break;
+
+ case 2:
+ do {
+ *--cp = to_char(val & 1);
+ val >>= 1;
+ } while (val);
+ break;
+
+ case 8:
+ do {
+ *--cp = to_char(val & 7);
+ val >>= 3;
+ } while (val);
+ if (octzero && *cp != '0')
+ *--cp = '0';
+ break;
+
+ case 16:
+ do {
+ *--cp = xdigs[val & 15];
+ val >>= 4;
+ } while (val);
+ break;
+
+ default:
+ abort();
+ }
+ return (cp);
+}
diff --git a/lib/printf/printflocal.h b/lib/printf/printflocal.h
new file mode 100644
index 0000000..4b03091
--- /dev/null
+++ b/lib/printf/printflocal.h
@@ -0,0 +1,105 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "compiler.h"
+#include "printfrr.h"
+
+/*
+ * Flags used during conversion.
+ */
+#define ALT 0x001 /* alternate form */
+#define LADJUST 0x004 /* left adjustment */
+#define LONGDBL 0x008 /* long double */
+#define LONGINT 0x010 /* long integer */
+#define LLONGINT 0x020 /* long long integer */
+#define SHORTINT 0x040 /* short integer */
+#define ZEROPAD 0x080 /* zero (as opposed to blank) pad */
+#define FPT 0x100 /* Floating point number */
+#define GROUPING 0x200 /* use grouping ("'" flag) */
+ /* C99 additional size modifiers: */
+#define SIZET 0x400 /* size_t */
+#define PTRDIFFT 0x800 /* ptrdiff_t */
+#define INTMAXT 0x1000 /* intmax_t */
+#define CHARINT 0x2000 /* print char using int format */
+
+/*
+ * Macros for converting digits to letters and vice versa
+ */
+#define to_digit(c) ((c) - '0')
+#define is_digit(c) ((unsigned)to_digit(c) <= 9)
+#define to_char(n) ((n) + '0')
+
+/* Size of the static argument table. */
+#define STATIC_ARG_TBL_SIZE 8
+
+union arg {
+ int intarg;
+ u_int uintarg;
+ long longarg;
+ u_long ulongarg;
+ long long longlongarg;
+ unsigned long long ulonglongarg;
+ ptrdiff_t ptrdiffarg;
+ size_t sizearg;
+ intmax_t intmaxarg;
+ uintmax_t uintmaxarg;
+ void *pvoidarg;
+ char *pchararg;
+ signed char *pschararg;
+ short *pshortarg;
+ int *pintarg;
+ long *plongarg;
+ long long *plonglongarg;
+ ptrdiff_t *pptrdiffarg;
+ ssize_t *pssizearg;
+ intmax_t *pintmaxarg;
+#ifndef NO_FLOATING_POINT
+ double doublearg;
+ long double longdoublearg;
+#endif
+ wint_t wintarg;
+ wchar_t *pwchararg;
+};
+
+/* Handle positional parameters. */
+int _frr_find_arguments(const char *, va_list, union arg **) DSO_LOCAL;
+#ifdef WCHAR_SUPPORT
+int _frr_find_warguments(const wchar_t *, va_list, union arg **) DSO_LOCAL;
+#endif
+
+/* returns number of bytes needed for full output, or -1 */
+ssize_t printfrr_extp(struct fbuf *, struct printfrr_eargs *ea, const void *)
+ DSO_LOCAL;
+ssize_t printfrr_exti(struct fbuf *, struct printfrr_eargs *ea, uintmax_t)
+ DSO_LOCAL;
diff --git a/lib/printf/vfprintf.c b/lib/printf/vfprintf.c
new file mode 100644
index 0000000..78f8be0
--- /dev/null
+++ b/lib/printf/vfprintf.c
@@ -0,0 +1,840 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * Copyright (c) 2011 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by David Chisnall
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_SYS_CDEFS_H
+#include <sys/cdefs.h>
+#endif
+
+/*
+ * Actual printf innards.
+ *
+ * This code is large and complicated...
+ */
+
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include <stdarg.h>
+
+#include "printflocal.h"
+
+#define CHAR char
+#include "printfcommon.h"
+
+#ifdef WCHAR_SUPPORT
+/*
+ * Convert a wide character string argument for the %ls format to a multibyte
+ * string representation. If not -1, prec specifies the maximum number of
+ * bytes to output, and also means that we can't assume that the wide char.
+ * string ends is null-terminated.
+ */
+static char *
+__wcsconv(wchar_t *wcsarg, int prec)
+{
+ static const mbstate_t initial;
+ mbstate_t mbs;
+ char buf[MB_LEN_MAX];
+ wchar_t *p;
+ char *convbuf;
+ size_t clen, nbytes;
+
+ /* Allocate space for the maximum number of bytes we could output. */
+ if (prec < 0) {
+ p = wcsarg;
+ mbs = initial;
+ nbytes = wcsrtombs(NULL, (const wchar_t **)&p, 0, &mbs);
+ if (nbytes == (size_t)-1)
+ return NULL;
+ } else {
+ /*
+ * Optimisation: if the output precision is small enough,
+ * just allocate enough memory for the maximum instead of
+ * scanning the string.
+ */
+ if (prec < 128)
+ nbytes = prec;
+ else {
+ nbytes = 0;
+ p = wcsarg;
+ mbs = initial;
+ for (;;) {
+ clen = wcrtomb(buf, *p++, &mbs);
+ if (clen == 0 || clen == (size_t)-1 ||
+ nbytes + clen > (size_t)prec)
+ break;
+ nbytes += clen;
+ }
+ }
+ }
+ if ((convbuf = malloc(nbytes + 1)) == NULL)
+ return NULL;
+
+ /* Fill the output buffer. */
+ p = wcsarg;
+ mbs = initial;
+ if ((nbytes = wcsrtombs(convbuf, (const wchar_t **)&p,
+ nbytes, &mbs)) == (size_t)-1) {
+ free(convbuf);
+ return NULL;
+ }
+ convbuf[nbytes] = '\0';
+ return (convbuf);
+}
+#endif /* WCHAR_SUPPORT */
+
+/*
+ * The size of the buffer we use as scratch space for integer
+ * conversions, among other things. We need enough space to
+ * write a uintmax_t in octal (plus one byte).
+ */
+#if UINTMAX_MAX <= UINT64_MAX
+#define BUF 80
+#else
+#error "BUF must be large enough to format a uintmax_t"
+#endif
+
+/*
+ * Non-MT-safe version
+ */
+ssize_t
+vbprintfrr(struct fbuf *cb_in, const char *fmt0, va_list ap)
+{
+ const char *fmt; /* format string */
+ int ch; /* character from fmt */
+ int n, n2; /* handy integer (short term usage) */
+ const char *cp; /* handy char pointer (short term usage) */
+ int flags; /* flags as above */
+ int ret; /* return value accumulator */
+ int width; /* width from format (%8d), or 0 */
+ int prec; /* precision from format; <0 for N/A */
+ int saved_errno;
+ char sign; /* sign prefix (' ', '+', '-', or \0) */
+
+ u_long ulval = 0; /* integer arguments %[diouxX] */
+ uintmax_t ujval = 0; /* %j, %ll, %q, %t, %z integers */
+ void *ptrval; /* %p */
+ int base; /* base for [diouxX] conversion */
+ int dprec; /* a copy of prec if [diouxX], 0 otherwise */
+ int realsz; /* field size expanded by dprec, sign, etc */
+ int size; /* size of converted field or string */
+ int prsize; /* max size of printed field */
+ const char *xdigs; /* digits for %[xX] conversion */
+ struct io_state io; /* I/O buffering state */
+ char buf[BUF]; /* buffer with space for digits of uintmax_t */
+ char ox[2]; /* space for 0x; ox[1] is either x, X, or \0 */
+ union arg *argtable; /* args, built due to positional arg */
+ union arg statargtable [STATIC_ARG_TBL_SIZE];
+ int nextarg; /* 1-based argument index */
+ va_list orgap; /* original argument pointer */
+ char *convbuf; /* wide to multibyte conversion result */
+ char *extstart = NULL; /* where printfrr_ext* started printing */
+ struct fbuf cb_copy, *cb;
+ struct fmt_outpos *opos;
+
+ static const char xdigs_lower[16] = "0123456789abcdef";
+ static const char xdigs_upper[16] = "0123456789ABCDEF";
+
+ /* BEWARE, these `goto error' on error. */
+#define PRINT(ptr, len) { \
+ if (io_print(&io, (ptr), (len))) \
+ goto error; \
+}
+#define PAD(howmany, with) { \
+ if (io_pad(&io, (howmany), (with))) \
+ goto error; \
+}
+#define PRINTANDPAD(p, ep, len, with) { \
+ if (io_printandpad(&io, (p), (ep), (len), (with))) \
+ goto error; \
+}
+#define FLUSH() do { } while (0)
+
+ /*
+ * Get the argument indexed by nextarg. If the argument table is
+ * built, use it to get the argument. If its not, get the next
+ * argument (and arguments must be gotten sequentially).
+ */
+#define GETARG(type) \
+ ((argtable != NULL) ? *((type*)(&argtable[nextarg++])) : \
+ (nextarg++, va_arg(ap, type)))
+
+ /*
+ * To extend shorts properly, we need both signed and unsigned
+ * argument extraction methods.
+ */
+#define SARG() \
+ (flags&LONGINT ? GETARG(long) : \
+ flags&SHORTINT ? (long)(short)GETARG(int) : \
+ flags&CHARINT ? (long)(signed char)GETARG(int) : \
+ (long)GETARG(int))
+#define UARG() \
+ (flags&LONGINT ? GETARG(u_long) : \
+ flags&SHORTINT ? (u_long)(u_short)GETARG(int) : \
+ flags&CHARINT ? (u_long)(u_char)GETARG(int) : \
+ (u_long)GETARG(u_int))
+#define INTMAX_SIZE (INTMAXT|SIZET|PTRDIFFT|LLONGINT|LONGDBL)
+#define SJARG() \
+ (flags&LONGDBL ? GETARG(int64_t) : \
+ flags&INTMAXT ? GETARG(intmax_t) : \
+ flags&SIZET ? (intmax_t)GETARG(ssize_t) : \
+ flags&PTRDIFFT ? (intmax_t)GETARG(ptrdiff_t) : \
+ (intmax_t)GETARG(long long))
+#define UJARG() \
+ (flags&LONGDBL ? GETARG(uint64_t) : \
+ flags&INTMAXT ? GETARG(uintmax_t) : \
+ flags&SIZET ? (uintmax_t)GETARG(size_t) : \
+ flags&PTRDIFFT ? (uintmax_t)GETARG(ptrdiff_t) : \
+ (uintmax_t)GETARG(unsigned long long))
+
+ /*
+ * Get * arguments, including the form *nn$. Preserve the nextarg
+ * that the argument can be gotten once the type is determined.
+ */
+#define GETASTER(val) \
+ n2 = 0; \
+ cp = fmt; \
+ while (is_digit(*cp)) { \
+ n2 = 10 * n2 + to_digit(*cp); \
+ cp++; \
+ } \
+ if (*cp == '$') { \
+ int hold = nextarg; \
+ if (argtable == NULL) { \
+ argtable = statargtable; \
+ if (_frr_find_arguments (fmt0, orgap, &argtable)) { \
+ ret = EOF; \
+ goto error; \
+ } \
+ } \
+ nextarg = n2; \
+ val = GETARG (int); \
+ nextarg = hold; \
+ fmt = ++cp; \
+ } else { \
+ val = GETARG (int); \
+ }
+
+ xdigs = xdigs_lower;
+ saved_errno = errno;
+ convbuf = NULL;
+ fmt = (char *)fmt0;
+ argtable = NULL;
+ nextarg = 1;
+ va_copy(orgap, ap);
+
+ if (cb_in) {
+ /* prevent printfrr exts from polluting cb->outpos */
+ cb_copy = *cb_in;
+ cb_copy.outpos = NULL;
+ cb_copy.outpos_n = cb_copy.outpos_i = 0;
+ cb = &cb_copy;
+ } else
+ cb = NULL;
+
+ io_init(&io, cb);
+ ret = 0;
+
+ /*
+ * Scan the format for conversions (`%' character).
+ */
+ for (;;) {
+ for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++)
+ /* void */;
+ if ((n = fmt - cp) != 0) {
+ if ((unsigned)ret + n > INT_MAX) {
+ ret = EOF;
+ errno = EOVERFLOW;
+ goto error;
+ }
+ PRINT(cp, n);
+ ret += n;
+ }
+ if (ch == '\0')
+ goto done;
+ fmt++; /* skip over '%' */
+
+ flags = 0;
+ dprec = 0;
+ width = -1;
+ prec = -1;
+ sign = '\0';
+ ox[1] = '\0';
+
+ if (cb_in && cb_in->outpos_i < cb_in->outpos_n)
+ opos = &cb_in->outpos[cb_in->outpos_i];
+ else
+ opos = NULL;
+
+rflag: ch = *fmt++;
+reswitch: switch (ch) {
+ case ' ':
+ /*-
+ * ``If the space and + flags both appear, the space
+ * flag will be ignored.''
+ * -- ANSI X3J11
+ */
+ if (!sign)
+ sign = ' ';
+ goto rflag;
+ case '#':
+ flags |= ALT;
+ goto rflag;
+ case '*':
+ /*-
+ * ``A negative field width argument is taken as a
+ * - flag followed by a positive field width.''
+ * -- ANSI X3J11
+ * They don't exclude field widths read from args.
+ */
+ GETASTER (width);
+ if (width >= 0)
+ goto rflag;
+ width = -width;
+ /* FALLTHROUGH */
+ case '-':
+ flags |= LADJUST;
+ goto rflag;
+ case '+':
+ sign = '+';
+ goto rflag;
+ case '\'':
+ flags |= GROUPING;
+ goto rflag;
+ case '.':
+ if ((ch = *fmt++) == '*') {
+ GETASTER (prec);
+ goto rflag;
+ }
+ prec = 0;
+ while (is_digit(ch)) {
+ prec = 10 * prec + to_digit(ch);
+ ch = *fmt++;
+ }
+ goto reswitch;
+ case '0':
+ /*-
+ * ``Note that 0 is taken as a flag, not as the
+ * beginning of a field width.''
+ * -- ANSI X3J11
+ */
+ flags |= ZEROPAD;
+ goto rflag;
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ n = 0;
+ do {
+ n = 10 * n + to_digit(ch);
+ ch = *fmt++;
+ } while (is_digit(ch));
+ if (ch == '$') {
+ nextarg = n;
+ if (argtable == NULL) {
+ argtable = statargtable;
+ if (_frr_find_arguments (fmt0, orgap,
+ &argtable)) {
+ ret = EOF;
+ goto error;
+ }
+ }
+ goto rflag;
+ }
+ width = n;
+ goto reswitch;
+ case 'L':
+ flags |= LONGDBL;
+ goto rflag;
+ case 'h':
+ if (flags & SHORTINT) {
+ flags &= ~SHORTINT;
+ flags |= CHARINT;
+ } else
+ flags |= SHORTINT;
+ goto rflag;
+ case 'j':
+ flags |= INTMAXT;
+ goto rflag;
+ case 'l':
+ if (flags & LONGINT) {
+ flags &= ~LONGINT;
+ flags |= LLONGINT;
+ } else
+ flags |= LONGINT;
+ goto rflag;
+ case 'q':
+ flags |= LLONGINT; /* not necessarily */
+ goto rflag;
+ case 't':
+ flags |= PTRDIFFT;
+ goto rflag;
+ case 'z':
+ flags |= SIZET;
+ goto rflag;
+ case 'B':
+ case 'b':
+ if (flags & INTMAX_SIZE)
+ ujval = UJARG();
+ else
+ ulval = UARG();
+ base = 2;
+ /* leading 0b/B only if non-zero */
+ if (flags & ALT &&
+ (flags & INTMAX_SIZE ? ujval != 0 : ulval != 0))
+ ox[1] = ch;
+ goto nosign;
+ break;
+ case 'C':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'c':
+#ifdef WCHAR_SUPPORT
+ if (flags & LONGINT) {
+ static const mbstate_t initial;
+ mbstate_t mbs;
+ size_t mbseqlen;
+
+ mbs = initial;
+ mbseqlen = wcrtomb(cp = buf,
+ (wchar_t)GETARG(wint_t), &mbs);
+ if (mbseqlen == (size_t)-1) {
+ goto error;
+ }
+ size = (int)mbseqlen;
+ } else
+#endif /* WCHAR_SUPPORT */
+ {
+ buf[0] = GETARG(int);
+ cp = buf;
+ size = 1;
+ }
+ sign = '\0';
+ break;
+ case 'D':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'd':
+ case 'i':
+ if (flags & INTMAX_SIZE)
+ ujval = SJARG();
+ else
+ ulval = SARG();
+
+ if (printfrr_ext_char(fmt[0])) {
+ struct printfrr_eargs ea = {
+ .fmt = fmt,
+ .precision = prec,
+ .width = width,
+ .alt_repr = !!(flags & ALT),
+ .leftadj = !!(flags & LADJUST),
+ };
+
+ if (cb)
+ extstart = cb->pos;
+
+ size = printfrr_exti(cb, &ea,
+ (flags & INTMAX_SIZE) ? ujval
+ : (uintmax_t)ulval);
+ if (size >= 0) {
+ fmt = ea.fmt;
+ width = ea.width;
+ goto ext_printed;
+ }
+ }
+ if (flags & INTMAX_SIZE) {
+ if ((intmax_t)ujval < 0) {
+ ujval = -ujval;
+ sign = '-';
+ }
+ } else {
+ if ((long)ulval < 0) {
+ ulval = -ulval;
+ sign = '-';
+ }
+ }
+ base = 10;
+ goto number;
+#ifndef NO_FLOATING_POINT
+ case 'a':
+ case 'A':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G':
+ if (flags & LONGDBL) {
+ long double arg = GETARG(long double);
+ char fmt[6] = "%.*L";
+ fmt[4] = ch;
+ fmt[5] = '\0';
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ snprintf(buf, sizeof(buf), fmt, prec, arg);
+#pragma GCC diagnostic pop
+ } else {
+ double arg = GETARG(double);
+ char fmt[5] = "%.*";
+ fmt[3] = ch;
+ fmt[4] = '\0';
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ snprintf(buf, sizeof(buf), fmt, prec, arg);
+#pragma GCC diagnostic pop
+ }
+ cp = buf;
+ /* for proper padding */
+ if (*cp == '-') {
+ cp++;
+ sign = '-';
+ }
+ /* "inf" */
+ if (!is_digit(*cp) && *cp != '.')
+ flags &= ~ZEROPAD;
+ size = strlen(buf);
+ break;
+#endif
+ case 'm':
+ cp = strerror(saved_errno);
+ size = (prec >= 0) ? strnlen(cp, prec) : strlen(cp);
+ sign = '\0';
+ break;
+ case 'O':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'o':
+ if (flags & INTMAX_SIZE)
+ ujval = UJARG();
+ else
+ ulval = UARG();
+ base = 8;
+ goto nosign;
+ case 'p':
+ /*-
+ * ``The argument shall be a pointer to void. The
+ * value of the pointer is converted to a sequence
+ * of printable characters, in an implementation-
+ * defined manner.''
+ * -- ANSI X3J11
+ */
+ ptrval = GETARG(void *);
+ if (printfrr_ext_char(fmt[0])) {
+ struct printfrr_eargs ea = {
+ .fmt = fmt,
+ .precision = prec,
+ .width = width,
+ .alt_repr = !!(flags & ALT),
+ .leftadj = !!(flags & LADJUST),
+ };
+
+ if (cb)
+ extstart = cb->pos;
+
+ size = printfrr_extp(cb, &ea, ptrval);
+ if (size >= 0) {
+ fmt = ea.fmt;
+ width = ea.width;
+ goto ext_printed;
+ }
+ }
+ ujval = (uintmax_t)(uintptr_t)ptrval;
+ base = 16;
+ xdigs = xdigs_lower;
+ flags = flags | INTMAXT;
+ ox[1] = 'x';
+ goto nosign;
+ case 'S':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 's':
+#ifdef WCHAR_SUPPORT
+ if (flags & LONGINT) {
+ wchar_t *wcp;
+
+ if (convbuf != NULL)
+ free(convbuf);
+ if ((wcp = GETARG(wchar_t *)) == NULL)
+ cp = "(null)";
+ else {
+ convbuf = __wcsconv(wcp, prec);
+ if (convbuf == NULL) {
+ goto error;
+ }
+ cp = convbuf;
+ }
+ } else
+#endif
+ if ((cp = GETARG(char *)) == NULL)
+ cp = "(null)";
+ size = (prec >= 0) ? strnlen(cp, prec) : strlen(cp);
+ sign = '\0';
+ break;
+ case 'U':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'u':
+ if (flags & INTMAX_SIZE)
+ ujval = UJARG();
+ else
+ ulval = UARG();
+ base = 10;
+ goto nosign;
+ case 'X':
+ xdigs = xdigs_upper;
+ goto hex;
+ case 'x':
+ xdigs = xdigs_lower;
+hex:
+ if (flags & INTMAX_SIZE)
+ ujval = UJARG();
+ else
+ ulval = UARG();
+ base = 16;
+ /* leading 0x/X only if non-zero */
+ if (flags & ALT &&
+ (flags & INTMAX_SIZE ? ujval != 0 : ulval != 0))
+ ox[1] = ch;
+
+ flags &= ~GROUPING;
+ /* unsigned conversions */
+nosign: sign = '\0';
+ /*-
+ * ``... diouXx conversions ... if a precision is
+ * specified, the 0 flag will be ignored.''
+ * -- ANSI X3J11
+ */
+number: if ((dprec = prec) >= 0)
+ flags &= ~ZEROPAD;
+
+ /*-
+ * ``The result of converting a zero value with an
+ * explicit precision of zero is no characters.''
+ * -- ANSI X3J11
+ *
+ * ``The C Standard is clear enough as is. The call
+ * printf("%#.0o", 0) should print 0.''
+ * -- Defect Report #151
+ */
+ cp = buf + BUF;
+ if (flags & INTMAX_SIZE) {
+ if (ujval != 0 || prec != 0 ||
+ (flags & ALT && base == 8))
+ cp = __ujtoa(ujval, buf + BUF, base,
+ flags & ALT, xdigs);
+ } else {
+ if (ulval != 0 || prec != 0 ||
+ (flags & ALT && base == 8))
+ cp = __ultoa(ulval, buf + BUF, base,
+ flags & ALT, xdigs);
+ }
+ size = buf + BUF - cp;
+ if (size > BUF) /* should never happen */
+ abort();
+ break;
+ default: /* "%?" prints ?, unless ? is NUL */
+ if (ch == '\0')
+ goto done;
+ /* pretend it was %c with argument ch */
+ buf[0] = ch;
+ cp = buf;
+ size = 1;
+ sign = '\0';
+ opos = NULL;
+ break;
+ }
+
+ /*
+ * All reasonable formats wind up here. At this point, `cp'
+ * points to a string which (if not flags&LADJUST) should be
+ * padded out to `width' places. If flags&ZEROPAD, it should
+ * first be prefixed by any sign or other prefix; otherwise,
+ * it should be blank padded before the prefix is emitted.
+ * After any left-hand padding and prefixing, emit zeroes
+ * required by a decimal [diouxX] precision, then print the
+ * string proper, then emit zeroes required by any leftover
+ * floating precision; finally, if LADJUST, pad with blanks.
+ *
+ * Compute actual size, so we know how much to pad.
+ * size excludes decimal prec; realsz includes it.
+ */
+ if (width < 0)
+ width = 0;
+
+ realsz = dprec > size ? dprec : size;
+ if (sign)
+ realsz++;
+ if (ox[1])
+ realsz += 2;
+
+ prsize = width > realsz ? width : realsz;
+ if ((unsigned int)ret + prsize > INT_MAX) {
+ ret = EOF;
+ errno = EOVERFLOW;
+ goto error;
+ }
+
+ /* right-adjusting blank padding */
+ if ((flags & (LADJUST|ZEROPAD)) == 0)
+ PAD(width - realsz, blanks);
+
+ if (opos)
+ opos->off_start = cb->pos - cb->buf;
+
+ /* prefix */
+ if (sign)
+ PRINT(&sign, 1);
+
+ if (ox[1]) { /* ox[1] is either x, X, or \0 */
+ ox[0] = '0';
+ PRINT(ox, 2);
+ }
+
+ /* right-adjusting zero padding */
+ if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD)
+ PAD(width - realsz, zeroes);
+
+ /* the string or number proper */
+ /* leading zeroes from decimal precision */
+ PAD(dprec - size, zeroes);
+ PRINT(cp, size);
+
+ if (opos) {
+ opos->off_end = cb->pos - cb->buf;
+ cb_in->outpos_i++;
+ }
+
+ /* left-adjusting padding (always blank) */
+ if (flags & LADJUST)
+ PAD(width - realsz, blanks);
+
+ /* finally, adjust ret */
+ ret += prsize;
+
+ FLUSH(); /* copy out the I/O vectors */
+ continue;
+
+ext_printed:
+ /* when we arrive here, a printfrr extension has written to cb
+ * (if non-NULL), but we still need to handle padding. The
+ * original cb->pos is in extstart; the return value from the
+ * ext is in size.
+ *
+ * Keep analogous to code above please.
+ */
+
+ if (width < 0)
+ width = 0;
+
+ realsz = size;
+ prsize = width > realsz ? width : realsz;
+ if ((unsigned int)ret + prsize > INT_MAX) {
+ ret = EOF;
+ errno = EOVERFLOW;
+ goto error;
+ }
+
+ /* right-adjusting blank padding - need to move the chars
+ * that the extension has already written. Should be very
+ * rare.
+ */
+ if (cb && width > size && (flags & (LADJUST|ZEROPAD)) == 0) {
+ size_t nwritten = cb->pos - extstart;
+ size_t navail = cb->buf + cb->len - extstart;
+ size_t npad = width - realsz;
+ size_t nmove;
+
+ if (navail < npad)
+ navail = 0;
+ else
+ navail -= npad;
+ nmove = MIN(nwritten, navail);
+
+ memmove(extstart + npad, extstart, nmove);
+
+ cb->pos = extstart;
+ PAD(npad, blanks);
+ cb->pos += nmove;
+ extstart += npad;
+ }
+
+ io.avail = cb ? cb->len - (cb->pos - cb->buf) : 0;
+
+ if (opos && extstart <= cb->pos) {
+ opos->off_start = extstart - cb->buf;
+ opos->off_end = cb->pos - cb->buf;
+ cb_in->outpos_i++;
+ }
+
+ /* left-adjusting padding (always blank) */
+ if (flags & LADJUST)
+ PAD(width - realsz, blanks);
+
+ /* finally, adjust ret */
+ ret += prsize;
+
+ FLUSH(); /* copy out the I/O vectors */
+ }
+done:
+ FLUSH();
+error:
+ va_end(orgap);
+ if (convbuf != NULL)
+ free(convbuf);
+ if ((argtable != NULL) && (argtable != statargtable))
+ free (argtable);
+ if (cb_in)
+ cb_in->pos = cb->pos;
+ return (ret);
+ /* NOTREACHED */
+}
+
diff --git a/lib/printfrr.h b/lib/printfrr.h
new file mode 100644
index 0000000..b8bef56
--- /dev/null
+++ b/lib/printfrr.h
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2019 David Lamparter, for NetDEF, Inc.
+ */
+
+#ifndef _FRR_PRINTFRR_H
+#define _FRR_PRINTFRR_H
+
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+#include "compiler.h"
+#include "memory.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct fmt_outpos {
+ unsigned int off_start, off_end;
+};
+
+struct fbuf {
+ char *buf;
+ char *pos;
+ size_t len;
+
+ struct fmt_outpos *outpos;
+ size_t outpos_n, outpos_i;
+};
+
+#define at(a, b) PRINTFRR(a, b)
+#define atn(a, b) \
+ at(a, b) __attribute__((nonnull(1) _RET_NONNULL))
+#define atm(a, b) \
+ atn(a, b) __attribute__((malloc))
+
+/* return value is length needed for the full string (excluding \0) in all
+ * cases. The functions write as much as they can, but continue regardless,
+ * so the return value is independent of buffer length. Both bprintfrr and
+ * snprintf also accept NULL as output buffer.
+ */
+
+/* bprintfrr does NOT null terminate! use sparingly (only provided since it's
+ * the most direct interface) - useful for incrementally building long text
+ * (call bprintfrr repeatedly with the same buffer)
+ */
+ssize_t vbprintfrr(struct fbuf *out, const char *fmt, va_list) at(2, 0);
+ssize_t bprintfrr(struct fbuf *out, const char *fmt, ...) at(2, 3);
+
+/* these do null terminate like their snprintf cousins */
+ssize_t vsnprintfrr(char *out, size_t sz, const char *fmt, va_list) at(3, 0);
+ssize_t snprintfrr(char *out, size_t sz, const char *fmt, ...) at(3, 4);
+
+/* c = continue / concatenate (append at the end of the string)
+ * return value is would-be string length (regardless of buffer length),
+ * i.e. includes already written chars */
+ssize_t vcsnprintfrr(char *out, size_t sz, const char *fmt, va_list) at(3, 0);
+ssize_t csnprintfrr(char *out, size_t sz, const char *fmt, ...) at(3, 4);
+
+/* memory allocations don't fail in FRR, so you always get something here.
+ * (in case of error, returns a strdup of the format string) */
+char *vasprintfrr(struct memtype *mt, const char *fmt, va_list) atm(2, 0);
+char *asprintfrr(struct memtype *mt, const char *fmt, ...) atm(2, 3);
+
+/* try to use provided buffer (presumably from stack), allocate if it's too
+ * short. Must call XFREE(mt, return value) if return value != out.
+ */
+char *vasnprintfrr(struct memtype *mt, char *out, size_t sz,
+ const char *fmt, va_list) atn(4, 0);
+char *asnprintfrr(struct memtype *mt, char *out, size_t sz,
+ const char *fmt, ...) atn(4, 5);
+
+#define printfrr(fmt, ...) \
+ do { \
+ char buf[256], *out; \
+ out = asnprintfrr(MTYPE_TMP, buf, sizeof(buf), fmt, \
+ ##__VA_ARGS__); \
+ fputs(out, stdout); \
+ if (out != buf) \
+ XFREE(MTYPE_TMP, out); \
+ } while (0)
+
+#undef at
+#undef atm
+#undef atn
+
+/* extension specs must start with a capital letter (this is a restriction
+ * for both performance's and human understanding's sake.)
+ *
+ * Note that the entire thing mostly works because a letter directly following
+ * a %p print specifier is extremely unlikely to occur (why would you want to
+ * print "0x12345678HELLO"?) Normally, you'd expect spacing or punctuation
+ * after a placeholder. That also means that neither of those works well for
+ * extension purposes, e.g. "%p{foo}" is reasonable to see actually used.
+ *
+ * TODO: would be nice to support a "%pF%dF" specifier that consumes 2
+ * arguments, e.g. to pass an integer + a list of known values... can be
+ * done, but a bit tricky.
+ */
+#define printfrr_ext_char(ch) ((ch) >= 'A' && (ch) <= 'Z')
+
+struct printfrr_eargs;
+
+struct printfrr_ext {
+ /* embedded string to minimize cache line pollution */
+ char match[8];
+
+ /* both can be given, if not the code continues searching
+ * (you can do %pX and %dX in 2 different entries)
+ *
+ * return value: number of bytes that would be printed if the buffer
+ * was large enough. be careful about not under-reporting this;
+ * otherwise asnprintf() & co. will get broken. Returning -1 means
+ * something went wrong & default %p/%d handling should be executed.
+ *
+ * to consume extra input flags after %pXY, increment *fmt. It points
+ * at the first character after %pXY at entry. Convention is to make
+ * those flags lowercase letters or numbers.
+ */
+ ssize_t (*print_ptr)(struct fbuf *buf, struct printfrr_eargs *info,
+ const void *);
+ ssize_t (*print_int)(struct fbuf *buf, struct printfrr_eargs *info,
+ uintmax_t);
+};
+
+/* additional information passed to extended formatters */
+
+struct printfrr_eargs {
+ /* position in the format string. Points to directly after the
+ * extension specifier. Increment when consuming extra "flag
+ * characters".
+ */
+ const char *fmt;
+
+ /* %.1234x / %.*x
+ * not POSIX compatible when used with %p, will cause warnings from
+ * GCC & clang. Usable with %d. Not used by the printfrr() itself
+ * for extension specifiers, so essentially available as a "free"
+ * parameter. -1 if not specified. Value in the format string
+ * cannot be negative, but negative values can be passed with %.*x
+ */
+ int precision;
+
+ /* %1234x / %*x
+ * regular width specification. Internally handled by printfrr(), set
+ * to 0 if consumed by the extension in order to suppress standard
+ * width/padding behavior. 0 if not specified.
+ *
+ * NB: always positive, even if a negative value is passed in with
+ * %*x. (The sign is used for the - flag.)
+ */
+ int width;
+
+ /* %#x
+ * "alternate representation" flag, not POSIX compatible when used
+ * with %p or %d, will cause warnings from GCC & clang. Not used by
+ * printfrr() itself for extension specifiers.
+ */
+ bool alt_repr;
+
+ /* %-x
+ * left-pad flag. Internally handled by printfrr() if width is
+ * nonzero. Only use if the extension sets width to 0.
+ */
+ bool leftadj;
+};
+
+/* for any extension that needs a buffer length */
+
+static inline ssize_t printfrr_ext_len(struct printfrr_eargs *ea)
+{
+ ssize_t rv;
+
+ if (ea->precision >= 0)
+ rv = ea->precision;
+ else if (ea->width >= 0) {
+ rv = ea->width;
+ ea->width = -1;
+ } else
+ rv = -1;
+
+ return rv;
+}
+
+/* no locking - must be called when single threaded (e.g. at startup.)
+ * this restriction hopefully won't be a huge bother considering normal usage
+ * scenarios...
+ */
+void printfrr_ext_reg(const struct printfrr_ext *);
+
+#define printfrr_ext_autoreg_p(matchs, print_fn) \
+ static ssize_t print_fn(struct fbuf *, struct printfrr_eargs *, \
+ const void *); \
+ static const struct printfrr_ext _printext_##print_fn = { \
+ .match = matchs, \
+ .print_ptr = print_fn, \
+ }; \
+ static void _printreg_##print_fn(void) __attribute__((constructor)); \
+ static void _printreg_##print_fn(void) \
+ { \
+ printfrr_ext_reg(&_printext_##print_fn); \
+ } \
+ MACRO_REQUIRE_SEMICOLON()
+
+#define printfrr_ext_autoreg_i(matchs, print_fn) \
+ static ssize_t print_fn(struct fbuf *, struct printfrr_eargs *, \
+ uintmax_t); \
+ static const struct printfrr_ext _printext_##print_fn = { \
+ .match = matchs, \
+ .print_int = print_fn, \
+ }; \
+ static void _printreg_##print_fn(void) __attribute__((constructor)); \
+ static void _printreg_##print_fn(void) \
+ { \
+ printfrr_ext_reg(&_printext_##print_fn); \
+ } \
+ MACRO_REQUIRE_SEMICOLON()
+
+/* fbuf helper functions - note all 3 of these return the length that would
+ * be written regardless of how much space was available in the buffer, as
+ * needed for implementing printfrr extensions. (They also accept NULL buf
+ * for that.)
+ */
+
+static inline ssize_t bputs(struct fbuf *buf, const char *str)
+{
+ size_t len = strlen(str);
+ size_t ncopy;
+
+ if (!buf)
+ return len;
+
+ ncopy = MIN(len, (size_t)(buf->buf + buf->len - buf->pos));
+ memcpy(buf->pos, str, ncopy);
+ buf->pos += ncopy;
+
+ return len;
+}
+
+static inline ssize_t bputch(struct fbuf *buf, char ch)
+{
+ if (buf && buf->pos < buf->buf + buf->len)
+ *buf->pos++ = ch;
+ return 1;
+}
+
+static inline ssize_t bputhex(struct fbuf *buf, uint8_t val)
+{
+ static const char hexch[] = "0123456789abcdef";
+
+ if (buf && buf->pos < buf->buf + buf->len)
+ *buf->pos++ = hexch[(val >> 4) & 0xf];
+ if (buf && buf->pos < buf->buf + buf->len)
+ *buf->pos++ = hexch[val & 0xf];
+ return 2;
+}
+
+/* %pVA extension, equivalent to Linux kernel %pV */
+
+struct va_format {
+ const char *fmt;
+ va_list *va;
+};
+
+#ifdef _FRR_ATTRIBUTE_PRINTFRR
+#pragma FRR printfrr_ext "%pFB" (struct fbuf *)
+#pragma FRR printfrr_ext "%pVA" (struct va_format *)
+
+#pragma FRR printfrr_ext "%pHX" (signed char *)
+#pragma FRR printfrr_ext "%pHX" (unsigned char *)
+#pragma FRR printfrr_ext "%pHX" (void *)
+#pragma FRR printfrr_ext "%pHS" (signed char *)
+#pragma FRR printfrr_ext "%pHS" (unsigned char *)
+#pragma FRR printfrr_ext "%pHS" (void *)
+
+#pragma FRR printfrr_ext "%pSE" (char *)
+#pragma FRR printfrr_ext "%pSQ" (char *)
+
+#pragma FRR printfrr_ext "%pTS" (struct timespec *)
+#pragma FRR printfrr_ext "%pTV" (struct timeval *)
+#pragma FRR printfrr_ext "%pTT" (time_t *)
+#endif
+
+/* when using non-ISO-C compatible extension specifiers... */
+
+#ifdef _FRR_ATTRIBUTE_PRINTFRR
+#define FMT_NSTD_BEGIN
+#define FMT_NSTD_END
+#else /* !_FRR_ATTRIBUTE_PRINTFRR */
+#define FMT_NSTD_BEGIN \
+ _Pragma("GCC diagnostic push") \
+ _Pragma("GCC diagnostic ignored \"-Wformat\"") \
+ /* end */
+#define FMT_NSTD_END \
+ _Pragma("GCC diagnostic pop") \
+ /* end */
+#endif
+
+#define FMT_NSTD(expr) \
+ ({ \
+ FMT_NSTD_BEGIN \
+ typeof(expr) _v; \
+ _v = expr; \
+ FMT_NSTD_END \
+ _v; \
+ }) \
+ /* end */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif