diff options
Diffstat (limited to 'lib/strformat.c')
-rw-r--r-- | lib/strformat.c | 599 |
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); +} |