summaryrefslogtreecommitdiffstats
path: root/lib/strformat.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/strformat.c')
-rw-r--r--lib/strformat.c599
1 files changed, 599 insertions, 0 deletions
diff --git a/lib/strformat.c b/lib/strformat.c
new file mode 100644
index 0000000..d941a7f
--- /dev/null
+++ b/lib/strformat.c
@@ -0,0 +1,599 @@
+/*
+ * Copyright (c) 2019 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "compiler.h"
+
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+
+#include "printfrr.h"
+#include "monotime.h"
+
+printfrr_ext_autoreg_p("HX", printfrr_hexdump);
+static ssize_t printfrr_hexdump(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ ssize_t ret = 0;
+ ssize_t input_len = printfrr_ext_len(ea);
+ char sep = ' ';
+ const uint8_t *pos, *end;
+
+ if (ea->fmt[0] == 'c') {
+ ea->fmt++;
+ sep = ':';
+ } else if (ea->fmt[0] == 'n') {
+ ea->fmt++;
+ sep = '\0';
+ }
+
+ if (input_len < 0)
+ return 0;
+
+ for (pos = ptr, end = pos + input_len; pos < end; pos++) {
+ if (sep && pos != ptr)
+ ret += bputch(buf, sep);
+ ret += bputhex(buf, *pos);
+ }
+
+ return ret;
+}
+
+/* string analog for hexdumps / the "this." in ("74 68 69 73 0a |this.|") */
+
+printfrr_ext_autoreg_p("HS", printfrr_hexdstr);
+static ssize_t printfrr_hexdstr(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ ssize_t ret = 0;
+ ssize_t input_len = printfrr_ext_len(ea);
+ const uint8_t *pos, *end;
+
+ if (input_len < 0)
+ return 0;
+
+ for (pos = ptr, end = pos + input_len; pos < end; pos++) {
+ if (*pos >= 0x20 && *pos < 0x7f)
+ ret += bputch(buf, *pos);
+ else
+ ret += bputch(buf, '.');
+ }
+
+ return ret;
+}
+
+enum escape_flags {
+ ESC_N_R_T = (1 << 0), /* use \n \r \t instead of \x0a ...*/
+ ESC_SPACE = (1 << 1), /* \ */
+ ESC_BACKSLASH = (1 << 2), /* \\ */
+ ESC_DBLQUOTE = (1 << 3), /* \" */
+ ESC_SGLQUOTE = (1 << 4), /* \' */
+ ESC_BACKTICK = (1 << 5), /* \` */
+ ESC_DOLLAR = (1 << 6), /* \$ */
+ ESC_CLBRACKET = (1 << 7), /* \] for RFC5424 syslog */
+ ESC_OTHER = (1 << 8), /* remaining non-alpha */
+
+ ESC_ALL = ESC_N_R_T | ESC_SPACE | ESC_BACKSLASH | ESC_DBLQUOTE
+ | ESC_SGLQUOTE | ESC_DOLLAR | ESC_OTHER,
+ ESC_QUOTSTRING = ESC_N_R_T | ESC_BACKSLASH | ESC_DBLQUOTE,
+ /* if needed: ESC_SHELL = ... */
+};
+
+static ssize_t bquote(struct fbuf *buf, const uint8_t *pos, size_t len,
+ unsigned int flags)
+{
+ ssize_t ret = 0;
+ const uint8_t *end = pos + len;
+
+ for (; pos < end; pos++) {
+ /* here's to hoping this might be a bit faster... */
+ if (__builtin_expect(!!isalnum(*pos), 1)) {
+ ret += bputch(buf, *pos);
+ continue;
+ }
+
+ switch (*pos) {
+ case '%':
+ case '+':
+ case ',':
+ case '-':
+ case '.':
+ case '/':
+ case ':':
+ case '@':
+ case '_':
+ ret += bputch(buf, *pos);
+ continue;
+
+ case '\r':
+ if (!(flags & ESC_N_R_T))
+ break;
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, 'r');
+ continue;
+ case '\n':
+ if (!(flags & ESC_N_R_T))
+ break;
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, 'n');
+ continue;
+ case '\t':
+ if (!(flags & ESC_N_R_T))
+ break;
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, 't');
+ continue;
+
+ case ' ':
+ if (flags & ESC_SPACE)
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, *pos);
+ continue;
+
+ case '\\':
+ if (flags & ESC_BACKSLASH)
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, *pos);
+ continue;
+
+ case '"':
+ if (flags & ESC_DBLQUOTE)
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, *pos);
+ continue;
+
+ case '\'':
+ if (flags & ESC_SGLQUOTE)
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, *pos);
+ continue;
+
+ case '`':
+ if (flags & ESC_BACKTICK)
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, *pos);
+ continue;
+
+ case '$':
+ if (flags & ESC_DOLLAR)
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, *pos);
+ continue;
+
+ case ']':
+ if (flags & ESC_CLBRACKET)
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, *pos);
+ continue;
+
+ /* remaining: !#&'()*;<=>?[^{|}~ */
+
+ default:
+ if (*pos >= 0x20 && *pos < 0x7f) {
+ if (flags & ESC_OTHER)
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, *pos);
+ continue;
+ }
+ }
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, 'x');
+ ret += bputhex(buf, *pos);
+ }
+
+ return ret;
+}
+
+printfrr_ext_autoreg_p("SE", printfrr_escape);
+static ssize_t printfrr_escape(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *vptr)
+{
+ ssize_t len = printfrr_ext_len(ea);
+ const uint8_t *ptr = vptr;
+ bool null_is_empty = false;
+
+ if (ea->fmt[0] == 'n') {
+ null_is_empty = true;
+ ea->fmt++;
+ }
+
+ if (!ptr) {
+ if (null_is_empty)
+ return 0;
+ return bputs(buf, "(null)");
+ }
+
+ if (len < 0)
+ len = strlen((const char *)ptr);
+
+ return bquote(buf, ptr, len, ESC_ALL);
+}
+
+printfrr_ext_autoreg_p("SQ", printfrr_quote);
+static ssize_t printfrr_quote(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *vptr)
+{
+ ssize_t len = printfrr_ext_len(ea);
+ const uint8_t *ptr = vptr;
+ ssize_t ret = 0;
+ bool null_is_empty = false;
+ bool do_quotes = false;
+ unsigned int flags = ESC_QUOTSTRING;
+
+ while (ea->fmt[0]) {
+ switch (ea->fmt[0]) {
+ case 'n':
+ null_is_empty = true;
+ ea->fmt++;
+ continue;
+ case 'q':
+ do_quotes = true;
+ ea->fmt++;
+ continue;
+ case 's':
+ flags |= ESC_CLBRACKET;
+ flags &= ~ESC_N_R_T;
+ ea->fmt++;
+ continue;
+ }
+ break;
+ }
+
+ if (!ptr) {
+ if (null_is_empty)
+ return bputs(buf, do_quotes ? "\"\"" : "");
+ return bputs(buf, "(null)");
+ }
+
+ if (len < 0)
+ len = strlen((const char *)ptr);
+
+ if (do_quotes)
+ ret += bputch(buf, '"');
+ ret += bquote(buf, ptr, len, flags);
+ if (do_quotes)
+ ret += bputch(buf, '"');
+ return ret;
+}
+
+static ssize_t printfrr_abstime(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags);
+static ssize_t printfrr_reltime(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags);
+
+ssize_t printfrr_time(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags)
+{
+ bool have_abs, have_anchor;
+
+ if (!(flags & TIMEFMT_PRESELECT)) {
+ switch (ea->fmt[0]) {
+ case 'I':
+ /* no bit set */
+ break;
+ case 'M':
+ flags |= TIMEFMT_MONOTONIC;
+ break;
+ case 'R':
+ flags |= TIMEFMT_REALTIME;
+ break;
+ default:
+ return bputs(buf,
+ "{invalid time format input specifier}");
+ }
+ ea->fmt++;
+
+ if (ea->fmt[0] == 's') {
+ flags |= TIMEFMT_SINCE;
+ ea->fmt++;
+ } else if (ea->fmt[0] == 'u') {
+ flags |= TIMEFMT_UNTIL;
+ ea->fmt++;
+ }
+ }
+
+ have_abs = !!(flags & TIMEFMT_ABSOLUTE);
+ have_anchor = !!(flags & TIMEFMT_ANCHORS);
+
+ if (have_abs ^ have_anchor)
+ return printfrr_abstime(buf, ea, ts, flags);
+ else
+ return printfrr_reltime(buf, ea, ts, flags);
+}
+
+static ssize_t do_subsec(struct fbuf *buf, const struct timespec *ts,
+ int precision, unsigned int flags)
+{
+ unsigned long long frac;
+
+ if (precision <= 0 || (flags & TIMEFMT_SECONDS))
+ return 0;
+
+ frac = ts->tv_nsec;
+ if (precision > 9)
+ precision = 9;
+ for (int i = precision; i < 9; i++)
+ frac /= 10;
+ return bprintfrr(buf, ".%0*llu", precision, frac);
+}
+
+static ssize_t printfrr_abstime(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags)
+{
+ struct timespec real_ts[1];
+ struct tm tm;
+ char cbuf[32] = ""; /* manpage says 26 for ctime_r */
+ ssize_t ret = 0;
+ int precision = ea->precision;
+
+ while (ea->fmt[0]) {
+ char ch = *ea->fmt++;
+
+ switch (ch) {
+ case 'p':
+ flags |= TIMEFMT_SPACE;
+ continue;
+ case 'i':
+ flags |= TIMEFMT_ISO8601;
+ continue;
+ }
+
+ ea->fmt--;
+ break;
+ }
+
+ if (flags & TIMEFMT_SKIP)
+ return 0;
+ if (!ts)
+ return bputch(buf, '-');
+
+ if (flags & TIMEFMT_REALTIME)
+ *real_ts = *ts;
+ else if (flags & TIMEFMT_MONOTONIC) {
+ struct timespec mono_now[1];
+
+ clock_gettime(CLOCK_REALTIME, real_ts);
+ clock_gettime(CLOCK_MONOTONIC, mono_now);
+
+ timespecsub(real_ts, mono_now, real_ts);
+ timespecadd(real_ts, ts, real_ts);
+ } else {
+ clock_gettime(CLOCK_REALTIME, real_ts);
+
+ if (flags & TIMEFMT_SINCE)
+ timespecsub(real_ts, ts, real_ts);
+ else /* flags & TIMEFMT_UNTIL */
+ timespecadd(real_ts, ts, real_ts);
+ }
+
+ localtime_r(&real_ts->tv_sec, &tm);
+
+ if (flags & TIMEFMT_ISO8601) {
+ if (flags & TIMEFMT_SPACE)
+ strftime(cbuf, sizeof(cbuf), "%Y-%m-%d %H:%M:%S", &tm);
+ else
+ strftime(cbuf, sizeof(cbuf), "%Y-%m-%dT%H:%M:%S", &tm);
+ ret += bputs(buf, cbuf);
+
+ if (precision == -1)
+ precision = 3;
+ ret += do_subsec(buf, real_ts, precision, flags);
+ } else {
+ size_t len;
+
+ asctime_r(&tm, cbuf);
+
+ len = strlen(cbuf);
+ if (!len)
+ /* WTF. */
+ return 0;
+ if (cbuf[len - 1] == '\n')
+ cbuf[len - 1] = '\0';
+
+ ret += bputs(buf, cbuf);
+ }
+ return ret;
+}
+
+static ssize_t printfrr_reltime(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags)
+{
+ struct timespec real_ts[1];
+ ssize_t ret = 0;
+ const char *space = "";
+ const char *dashes = "-";
+ int precision = ea->precision;
+
+ while (ea->fmt[0]) {
+ char ch = *ea->fmt++;
+
+ switch (ch) {
+ case 'p':
+ flags |= TIMEFMT_SPACE;
+ space = " ";
+ continue;
+ case 't':
+ flags |= TIMEFMT_BASIC;
+ continue;
+ case 'd':
+ flags |= TIMEFMT_DECIMAL;
+ continue;
+ case 'm':
+ flags |= TIMEFMT_MMSS;
+ dashes = "--:--";
+ continue;
+ case 'h':
+ flags |= TIMEFMT_HHMMSS;
+ dashes = "--:--:--";
+ continue;
+ case 'x':
+ flags |= TIMEFMT_DASHES;
+ continue;
+ }
+
+ ea->fmt--;
+ break;
+ }
+
+ if (flags & TIMEFMT_SKIP)
+ return 0;
+ if (!ts)
+ return bputch(buf, '-');
+
+ if (flags & TIMEFMT_ABSOLUTE) {
+ struct timespec anchor[1];
+
+ if (flags & TIMEFMT_REALTIME)
+ clock_gettime(CLOCK_REALTIME, anchor);
+ else
+ clock_gettime(CLOCK_MONOTONIC, anchor);
+ if (flags & TIMEFMT_UNTIL)
+ timespecsub(ts, anchor, real_ts);
+ else /* flags & TIMEFMT_SINCE */
+ timespecsub(anchor, ts, real_ts);
+ } else
+ *real_ts = *ts;
+
+ if (real_ts->tv_sec == 0 && real_ts->tv_nsec == 0 &&
+ (flags & TIMEFMT_DASHES))
+ return bputs(buf, dashes);
+
+ if (real_ts->tv_sec < 0) {
+ if (flags & TIMEFMT_DASHES)
+ return bputs(buf, dashes);
+
+ /* -0.3s is { -1s + 700ms } */
+ real_ts->tv_sec = -real_ts->tv_sec - 1;
+ real_ts->tv_nsec = 1000000000L - real_ts->tv_nsec;
+ if (real_ts->tv_nsec >= 1000000000L) {
+ real_ts->tv_sec++;
+ real_ts->tv_nsec -= 1000000000L;
+ }
+
+ /* all formats have a - make sense in front */
+ ret += bputch(buf, '-');
+ }
+
+ if (flags & TIMEFMT_DECIMAL) {
+ ret += bprintfrr(buf, "%lld", (long long)real_ts->tv_sec);
+ if (precision == -1)
+ precision = 3;
+ ret += do_subsec(buf, real_ts, precision, flags);
+ return ret;
+ }
+
+ /* these divisions may be slow on embedded boxes, hence only do the
+ * ones we need, plus the ?: zero check to hopefully skip zeros fast
+ */
+ lldiv_t min_sec = lldiv(real_ts->tv_sec, 60);
+
+ if (flags & TIMEFMT_MMSS) {
+ ret += bprintfrr(buf, "%02lld:%02lld", min_sec.quot,
+ min_sec.rem);
+ ret += do_subsec(buf, real_ts, precision, flags);
+ return ret;
+ }
+
+ lldiv_t hour_min = min_sec.quot ? lldiv(min_sec.quot, 60) : (lldiv_t){};
+
+ if (flags & TIMEFMT_HHMMSS) {
+ ret += bprintfrr(buf, "%02lld:%02lld:%02lld", hour_min.quot,
+ hour_min.rem, min_sec.rem);
+ ret += do_subsec(buf, real_ts, precision, flags);
+ return ret;
+ }
+
+ lldiv_t day_hour =
+ hour_min.quot ? lldiv(hour_min.quot, 24) : (lldiv_t){};
+ lldiv_t week_day =
+ day_hour.quot ? lldiv(day_hour.quot, 7) : (lldiv_t){};
+
+ /* if sub-second precision is not supported, return */
+ if (flags & TIMEFMT_BASIC) {
+ /* match frrtime_to_interval (without space flag) */
+ if (week_day.quot)
+ ret += bprintfrr(buf, "%lldw%s%lldd%s%02lldh",
+ week_day.quot, space, week_day.rem,
+ space, day_hour.rem);
+ else if (day_hour.quot)
+ ret += bprintfrr(buf, "%lldd%s%02lldh%s%02lldm",
+ day_hour.quot, space, day_hour.rem,
+ space, hour_min.rem);
+ else
+ ret += bprintfrr(buf, "%02lld:%02lld:%02lld",
+ hour_min.quot, hour_min.rem,
+ min_sec.rem);
+ /* no sub-seconds here */
+ return ret;
+ }
+
+ /* default format */
+ if (week_day.quot)
+ ret += bprintfrr(buf, "%lldw%s", week_day.quot, space);
+ if (week_day.rem || week_day.quot)
+ ret += bprintfrr(buf, "%lldd%s", week_day.rem, space);
+
+ ret += bprintfrr(buf, "%02lld:%02lld:%02lld", day_hour.rem,
+ hour_min.rem, min_sec.rem);
+
+ if (precision == -1)
+ precision = 3;
+ ret += do_subsec(buf, real_ts, precision, flags);
+ return ret;
+}
+
+printfrr_ext_autoreg_p("TS", printfrr_ts);
+static ssize_t printfrr_ts(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *vptr)
+{
+ const struct timespec *ts = vptr;
+
+ return printfrr_time(buf, ea, ts, 0);
+}
+
+printfrr_ext_autoreg_p("TV", printfrr_tv);
+static ssize_t printfrr_tv(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *vptr)
+{
+ const struct timeval *tv = vptr;
+ struct timespec ts;
+
+ if (!tv)
+ return printfrr_time(buf, ea, NULL, 0);
+
+ ts.tv_sec = tv->tv_sec;
+ ts.tv_nsec = tv->tv_usec * 1000;
+ return printfrr_time(buf, ea, &ts, 0);
+}
+
+printfrr_ext_autoreg_p("TT", printfrr_tt);
+static ssize_t printfrr_tt(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *vptr)
+{
+ const time_t *tt = vptr;
+ struct timespec ts;
+
+ if (!tt)
+ return printfrr_time(buf, ea, NULL, TIMEFMT_SECONDS);
+
+ ts.tv_sec = *tt;
+ ts.tv_nsec = 0;
+ return printfrr_time(buf, ea, &ts, TIMEFMT_SECONDS);
+}