]> git.proxmox.com Git - mirror_frr.git/blobdiff - lib/strformat.c
Merge pull request #13649 from donaldsharp/unlock_the_node_or_else
[mirror_frr.git] / lib / strformat.c
index 8e49a666fa74a6c67a605cbbaf718d94fcb3f629..87d1715d262c9948bcf51bb124ca07f81678f8fa 100644 (file)
@@ -1,28 +1,22 @@
+// SPDX-License-Identifier: ISC
 /*
  * 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)
+printfrr_ext_autoreg_p("HX", printfrr_hexdump);
 static ssize_t printfrr_hexdump(struct fbuf *buf, struct printfrr_eargs *ea,
                                const void *ptr)
 {
@@ -53,7 +47,7 @@ static ssize_t printfrr_hexdump(struct fbuf *buf, struct printfrr_eargs *ea,
 
 /* string analog for hexdumps / the "this." in ("74 68 69 73 0a  |this.|") */
 
-printfrr_ext_autoreg_p("HS", printfrr_hexdstr)
+printfrr_ext_autoreg_p("HS", printfrr_hexdstr);
 static ssize_t printfrr_hexdstr(struct fbuf *buf, struct printfrr_eargs *ea,
                                const void *ptr)
 {
@@ -73,3 +67,522 @@ static ssize_t printfrr_hexdstr(struct fbuf *buf, struct printfrr_eargs *ea,
 
        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);
+}