]> git.proxmox.com Git - mirror_frr.git/blame - lib/strformat.c
Merge pull request #13649 from donaldsharp/unlock_the_node_or_else
[mirror_frr.git] / lib / strformat.c
CommitLineData
acddc0ed 1// SPDX-License-Identifier: ISC
a4cb97a6
DL
2/*
3 * Copyright (c) 2019 David Lamparter, for NetDEF, Inc.
a4cb97a6
DL
4 */
5
6#ifdef HAVE_CONFIG_H
7#include "config.h"
8#endif
9
7798203f
DL
10#include "compiler.h"
11
a4cb97a6 12#include <string.h>
7798203f 13#include <ctype.h>
2c76ba43 14#include <time.h>
a4cb97a6
DL
15
16#include "printfrr.h"
2c76ba43 17#include "monotime.h"
a4cb97a6 18
54929fd3 19printfrr_ext_autoreg_p("HX", printfrr_hexdump);
a4cb97a6
DL
20static ssize_t printfrr_hexdump(struct fbuf *buf, struct printfrr_eargs *ea,
21 const void *ptr)
22{
23 ssize_t ret = 0;
24 ssize_t input_len = printfrr_ext_len(ea);
25 char sep = ' ';
26 const uint8_t *pos, *end;
27
28 if (ea->fmt[0] == 'c') {
29 ea->fmt++;
30 sep = ':';
31 } else if (ea->fmt[0] == 'n') {
32 ea->fmt++;
33 sep = '\0';
34 }
35
36 if (input_len < 0)
37 return 0;
38
39 for (pos = ptr, end = pos + input_len; pos < end; pos++) {
40 if (sep && pos != ptr)
41 ret += bputch(buf, sep);
42 ret += bputhex(buf, *pos);
43 }
44
45 return ret;
46}
47
48/* string analog for hexdumps / the "this." in ("74 68 69 73 0a |this.|") */
49
54929fd3 50printfrr_ext_autoreg_p("HS", printfrr_hexdstr);
a4cb97a6
DL
51static ssize_t printfrr_hexdstr(struct fbuf *buf, struct printfrr_eargs *ea,
52 const void *ptr)
53{
54 ssize_t ret = 0;
55 ssize_t input_len = printfrr_ext_len(ea);
56 const uint8_t *pos, *end;
57
58 if (input_len < 0)
59 return 0;
60
61 for (pos = ptr, end = pos + input_len; pos < end; pos++) {
62 if (*pos >= 0x20 && *pos < 0x7f)
63 ret += bputch(buf, *pos);
64 else
65 ret += bputch(buf, '.');
66 }
67
68 return ret;
69}
7798203f
DL
70
71enum escape_flags {
72 ESC_N_R_T = (1 << 0), /* use \n \r \t instead of \x0a ...*/
73 ESC_SPACE = (1 << 1), /* \ */
74 ESC_BACKSLASH = (1 << 2), /* \\ */
75 ESC_DBLQUOTE = (1 << 3), /* \" */
76 ESC_SGLQUOTE = (1 << 4), /* \' */
77 ESC_BACKTICK = (1 << 5), /* \` */
78 ESC_DOLLAR = (1 << 6), /* \$ */
79 ESC_CLBRACKET = (1 << 7), /* \] for RFC5424 syslog */
80 ESC_OTHER = (1 << 8), /* remaining non-alpha */
81
82 ESC_ALL = ESC_N_R_T | ESC_SPACE | ESC_BACKSLASH | ESC_DBLQUOTE
83 | ESC_SGLQUOTE | ESC_DOLLAR | ESC_OTHER,
84 ESC_QUOTSTRING = ESC_N_R_T | ESC_BACKSLASH | ESC_DBLQUOTE,
85 /* if needed: ESC_SHELL = ... */
86};
87
88static ssize_t bquote(struct fbuf *buf, const uint8_t *pos, size_t len,
89 unsigned int flags)
90{
91 ssize_t ret = 0;
92 const uint8_t *end = pos + len;
93
94 for (; pos < end; pos++) {
95 /* here's to hoping this might be a bit faster... */
96 if (__builtin_expect(!!isalnum(*pos), 1)) {
97 ret += bputch(buf, *pos);
98 continue;
99 }
100
101 switch (*pos) {
102 case '%':
103 case '+':
104 case ',':
105 case '-':
106 case '.':
107 case '/':
108 case ':':
109 case '@':
110 case '_':
111 ret += bputch(buf, *pos);
112 continue;
113
114 case '\r':
115 if (!(flags & ESC_N_R_T))
116 break;
117 ret += bputch(buf, '\\');
118 ret += bputch(buf, 'r');
119 continue;
120 case '\n':
121 if (!(flags & ESC_N_R_T))
122 break;
123 ret += bputch(buf, '\\');
124 ret += bputch(buf, 'n');
125 continue;
126 case '\t':
127 if (!(flags & ESC_N_R_T))
128 break;
129 ret += bputch(buf, '\\');
130 ret += bputch(buf, 't');
131 continue;
132
133 case ' ':
134 if (flags & ESC_SPACE)
135 ret += bputch(buf, '\\');
136 ret += bputch(buf, *pos);
137 continue;
138
139 case '\\':
140 if (flags & ESC_BACKSLASH)
141 ret += bputch(buf, '\\');
142 ret += bputch(buf, *pos);
143 continue;
144
145 case '"':
146 if (flags & ESC_DBLQUOTE)
147 ret += bputch(buf, '\\');
148 ret += bputch(buf, *pos);
149 continue;
150
151 case '\'':
152 if (flags & ESC_SGLQUOTE)
153 ret += bputch(buf, '\\');
154 ret += bputch(buf, *pos);
155 continue;
156
157 case '`':
158 if (flags & ESC_BACKTICK)
159 ret += bputch(buf, '\\');
160 ret += bputch(buf, *pos);
161 continue;
162
163 case '$':
164 if (flags & ESC_DOLLAR)
165 ret += bputch(buf, '\\');
166 ret += bputch(buf, *pos);
167 continue;
168
169 case ']':
170 if (flags & ESC_CLBRACKET)
171 ret += bputch(buf, '\\');
172 ret += bputch(buf, *pos);
173 continue;
174
175 /* remaining: !#&'()*;<=>?[^{|}~ */
176
177 default:
178 if (*pos >= 0x20 && *pos < 0x7f) {
179 if (flags & ESC_OTHER)
180 ret += bputch(buf, '\\');
181 ret += bputch(buf, *pos);
182 continue;
183 }
184 }
185 ret += bputch(buf, '\\');
186 ret += bputch(buf, 'x');
187 ret += bputhex(buf, *pos);
188 }
189
190 return ret;
191}
192
54929fd3 193printfrr_ext_autoreg_p("SE", printfrr_escape);
7798203f
DL
194static ssize_t printfrr_escape(struct fbuf *buf, struct printfrr_eargs *ea,
195 const void *vptr)
196{
197 ssize_t len = printfrr_ext_len(ea);
198 const uint8_t *ptr = vptr;
199 bool null_is_empty = false;
200
201 if (ea->fmt[0] == 'n') {
202 null_is_empty = true;
203 ea->fmt++;
204 }
205
206 if (!ptr) {
207 if (null_is_empty)
208 return 0;
209 return bputs(buf, "(null)");
210 }
211
212 if (len < 0)
213 len = strlen((const char *)ptr);
214
215 return bquote(buf, ptr, len, ESC_ALL);
216}
217
54929fd3 218printfrr_ext_autoreg_p("SQ", printfrr_quote);
7798203f
DL
219static ssize_t printfrr_quote(struct fbuf *buf, struct printfrr_eargs *ea,
220 const void *vptr)
221{
222 ssize_t len = printfrr_ext_len(ea);
223 const uint8_t *ptr = vptr;
224 ssize_t ret = 0;
225 bool null_is_empty = false;
226 bool do_quotes = false;
227 unsigned int flags = ESC_QUOTSTRING;
228
229 while (ea->fmt[0]) {
230 switch (ea->fmt[0]) {
231 case 'n':
232 null_is_empty = true;
233 ea->fmt++;
234 continue;
235 case 'q':
236 do_quotes = true;
237 ea->fmt++;
238 continue;
239 case 's':
240 flags |= ESC_CLBRACKET;
241 flags &= ~ESC_N_R_T;
242 ea->fmt++;
243 continue;
244 }
245 break;
246 }
247
248 if (!ptr) {
249 if (null_is_empty)
250 return bputs(buf, do_quotes ? "\"\"" : "");
251 return bputs(buf, "(null)");
252 }
253
254 if (len < 0)
255 len = strlen((const char *)ptr);
256
257 if (do_quotes)
258 ret += bputch(buf, '"');
259 ret += bquote(buf, ptr, len, flags);
260 if (do_quotes)
261 ret += bputch(buf, '"');
262 return ret;
263}
2c76ba43
DL
264
265static ssize_t printfrr_abstime(struct fbuf *buf, struct printfrr_eargs *ea,
266 const struct timespec *ts, unsigned int flags);
267static ssize_t printfrr_reltime(struct fbuf *buf, struct printfrr_eargs *ea,
268 const struct timespec *ts, unsigned int flags);
269
270ssize_t printfrr_time(struct fbuf *buf, struct printfrr_eargs *ea,
271 const struct timespec *ts, unsigned int flags)
272{
273 bool have_abs, have_anchor;
274
275 if (!(flags & TIMEFMT_PRESELECT)) {
276 switch (ea->fmt[0]) {
277 case 'I':
278 /* no bit set */
279 break;
280 case 'M':
281 flags |= TIMEFMT_MONOTONIC;
282 break;
283 case 'R':
284 flags |= TIMEFMT_REALTIME;
285 break;
286 default:
287 return bputs(buf,
288 "{invalid time format input specifier}");
289 }
290 ea->fmt++;
291
292 if (ea->fmt[0] == 's') {
293 flags |= TIMEFMT_SINCE;
294 ea->fmt++;
295 } else if (ea->fmt[0] == 'u') {
296 flags |= TIMEFMT_UNTIL;
297 ea->fmt++;
298 }
299 }
300
301 have_abs = !!(flags & TIMEFMT_ABSOLUTE);
302 have_anchor = !!(flags & TIMEFMT_ANCHORS);
303
304 if (have_abs ^ have_anchor)
305 return printfrr_abstime(buf, ea, ts, flags);
306 else
307 return printfrr_reltime(buf, ea, ts, flags);
308}
309
310static ssize_t do_subsec(struct fbuf *buf, const struct timespec *ts,
311 int precision, unsigned int flags)
312{
313 unsigned long long frac;
314
315 if (precision <= 0 || (flags & TIMEFMT_SECONDS))
316 return 0;
317
318 frac = ts->tv_nsec;
319 if (precision > 9)
320 precision = 9;
321 for (int i = precision; i < 9; i++)
322 frac /= 10;
323 return bprintfrr(buf, ".%0*llu", precision, frac);
324}
325
326static ssize_t printfrr_abstime(struct fbuf *buf, struct printfrr_eargs *ea,
327 const struct timespec *ts, unsigned int flags)
328{
329 struct timespec real_ts[1];
330 struct tm tm;
331 char cbuf[32] = ""; /* manpage says 26 for ctime_r */
332 ssize_t ret = 0;
333 int precision = ea->precision;
334
335 while (ea->fmt[0]) {
336 char ch = *ea->fmt++;
337
338 switch (ch) {
339 case 'p':
340 flags |= TIMEFMT_SPACE;
341 continue;
342 case 'i':
343 flags |= TIMEFMT_ISO8601;
344 continue;
345 }
346
347 ea->fmt--;
348 break;
349 }
350
351 if (flags & TIMEFMT_SKIP)
352 return 0;
782fe5e4
DL
353 if (!ts)
354 return bputch(buf, '-');
2c76ba43
DL
355
356 if (flags & TIMEFMT_REALTIME)
357 *real_ts = *ts;
358 else if (flags & TIMEFMT_MONOTONIC) {
359 struct timespec mono_now[1];
360
361 clock_gettime(CLOCK_REALTIME, real_ts);
362 clock_gettime(CLOCK_MONOTONIC, mono_now);
363
364 timespecsub(real_ts, mono_now, real_ts);
365 timespecadd(real_ts, ts, real_ts);
366 } else {
367 clock_gettime(CLOCK_REALTIME, real_ts);
368
369 if (flags & TIMEFMT_SINCE)
370 timespecsub(real_ts, ts, real_ts);
371 else /* flags & TIMEFMT_UNTIL */
372 timespecadd(real_ts, ts, real_ts);
373 }
374
375 localtime_r(&real_ts->tv_sec, &tm);
376
377 if (flags & TIMEFMT_ISO8601) {
378 if (flags & TIMEFMT_SPACE)
379 strftime(cbuf, sizeof(cbuf), "%Y-%m-%d %H:%M:%S", &tm);
380 else
381 strftime(cbuf, sizeof(cbuf), "%Y-%m-%dT%H:%M:%S", &tm);
382 ret += bputs(buf, cbuf);
383
384 if (precision == -1)
385 precision = 3;
386 ret += do_subsec(buf, real_ts, precision, flags);
387 } else {
388 size_t len;
389
390 asctime_r(&tm, cbuf);
391
392 len = strlen(cbuf);
393 if (!len)
394 /* WTF. */
395 return 0;
396 if (cbuf[len - 1] == '\n')
397 cbuf[len - 1] = '\0';
398
399 ret += bputs(buf, cbuf);
400 }
401 return ret;
402}
403
404static ssize_t printfrr_reltime(struct fbuf *buf, struct printfrr_eargs *ea,
405 const struct timespec *ts, unsigned int flags)
406{
407 struct timespec real_ts[1];
408 ssize_t ret = 0;
409 const char *space = "";
410 const char *dashes = "-";
411 int precision = ea->precision;
412
413 while (ea->fmt[0]) {
414 char ch = *ea->fmt++;
415
416 switch (ch) {
417 case 'p':
418 flags |= TIMEFMT_SPACE;
419 space = " ";
420 continue;
421 case 't':
422 flags |= TIMEFMT_BASIC;
423 continue;
424 case 'd':
425 flags |= TIMEFMT_DECIMAL;
426 continue;
427 case 'm':
428 flags |= TIMEFMT_MMSS;
429 dashes = "--:--";
430 continue;
431 case 'h':
432 flags |= TIMEFMT_HHMMSS;
433 dashes = "--:--:--";
434 continue;
435 case 'x':
436 flags |= TIMEFMT_DASHES;
437 continue;
438 }
439
440 ea->fmt--;
441 break;
442 }
443
444 if (flags & TIMEFMT_SKIP)
445 return 0;
782fe5e4
DL
446 if (!ts)
447 return bputch(buf, '-');
2c76ba43
DL
448
449 if (flags & TIMEFMT_ABSOLUTE) {
450 struct timespec anchor[1];
451
452 if (flags & TIMEFMT_REALTIME)
453 clock_gettime(CLOCK_REALTIME, anchor);
454 else
455 clock_gettime(CLOCK_MONOTONIC, anchor);
456 if (flags & TIMEFMT_UNTIL)
457 timespecsub(ts, anchor, real_ts);
458 else /* flags & TIMEFMT_SINCE */
459 timespecsub(anchor, ts, real_ts);
460 } else
461 *real_ts = *ts;
462
463 if (real_ts->tv_sec == 0 && real_ts->tv_nsec == 0 &&
464 (flags & TIMEFMT_DASHES))
465 return bputs(buf, dashes);
466
467 if (real_ts->tv_sec < 0) {
468 if (flags & TIMEFMT_DASHES)
469 return bputs(buf, dashes);
470
471 /* -0.3s is { -1s + 700ms } */
472 real_ts->tv_sec = -real_ts->tv_sec - 1;
473 real_ts->tv_nsec = 1000000000L - real_ts->tv_nsec;
474 if (real_ts->tv_nsec >= 1000000000L) {
475 real_ts->tv_sec++;
476 real_ts->tv_nsec -= 1000000000L;
477 }
478
479 /* all formats have a - make sense in front */
480 ret += bputch(buf, '-');
481 }
482
483 if (flags & TIMEFMT_DECIMAL) {
484 ret += bprintfrr(buf, "%lld", (long long)real_ts->tv_sec);
485 if (precision == -1)
486 precision = 3;
487 ret += do_subsec(buf, real_ts, precision, flags);
488 return ret;
489 }
490
491 /* these divisions may be slow on embedded boxes, hence only do the
492 * ones we need, plus the ?: zero check to hopefully skip zeros fast
493 */
494 lldiv_t min_sec = lldiv(real_ts->tv_sec, 60);
495
496 if (flags & TIMEFMT_MMSS) {
497 ret += bprintfrr(buf, "%02lld:%02lld", min_sec.quot,
498 min_sec.rem);
499 ret += do_subsec(buf, real_ts, precision, flags);
500 return ret;
501 }
502
503 lldiv_t hour_min = min_sec.quot ? lldiv(min_sec.quot, 60) : (lldiv_t){};
504
505 if (flags & TIMEFMT_HHMMSS) {
506 ret += bprintfrr(buf, "%02lld:%02lld:%02lld", hour_min.quot,
507 hour_min.rem, min_sec.rem);
508 ret += do_subsec(buf, real_ts, precision, flags);
509 return ret;
510 }
511
512 lldiv_t day_hour =
513 hour_min.quot ? lldiv(hour_min.quot, 24) : (lldiv_t){};
514 lldiv_t week_day =
515 day_hour.quot ? lldiv(day_hour.quot, 7) : (lldiv_t){};
516
517 /* if sub-second precision is not supported, return */
518 if (flags & TIMEFMT_BASIC) {
519 /* match frrtime_to_interval (without space flag) */
520 if (week_day.quot)
521 ret += bprintfrr(buf, "%lldw%s%lldd%s%02lldh",
522 week_day.quot, space, week_day.rem,
523 space, day_hour.rem);
524 else if (day_hour.quot)
525 ret += bprintfrr(buf, "%lldd%s%02lldh%s%02lldm",
526 day_hour.quot, space, day_hour.rem,
527 space, hour_min.rem);
528 else
529 ret += bprintfrr(buf, "%02lld:%02lld:%02lld",
530 hour_min.quot, hour_min.rem,
531 min_sec.rem);
532 /* no sub-seconds here */
533 return ret;
534 }
535
536 /* default format */
537 if (week_day.quot)
538 ret += bprintfrr(buf, "%lldw%s", week_day.quot, space);
539 if (week_day.rem || week_day.quot)
540 ret += bprintfrr(buf, "%lldd%s", week_day.rem, space);
541
542 ret += bprintfrr(buf, "%02lld:%02lld:%02lld", day_hour.rem,
543 hour_min.rem, min_sec.rem);
544
545 if (precision == -1)
546 precision = 3;
547 ret += do_subsec(buf, real_ts, precision, flags);
548 return ret;
549}
550
54929fd3 551printfrr_ext_autoreg_p("TS", printfrr_ts);
2c76ba43
DL
552static ssize_t printfrr_ts(struct fbuf *buf, struct printfrr_eargs *ea,
553 const void *vptr)
554{
555 const struct timespec *ts = vptr;
556
2c76ba43
DL
557 return printfrr_time(buf, ea, ts, 0);
558}
559
54929fd3 560printfrr_ext_autoreg_p("TV", printfrr_tv);
2c76ba43
DL
561static ssize_t printfrr_tv(struct fbuf *buf, struct printfrr_eargs *ea,
562 const void *vptr)
563{
564 const struct timeval *tv = vptr;
565 struct timespec ts;
566
567 if (!tv)
782fe5e4 568 return printfrr_time(buf, ea, NULL, 0);
2c76ba43
DL
569
570 ts.tv_sec = tv->tv_sec;
571 ts.tv_nsec = tv->tv_usec * 1000;
572 return printfrr_time(buf, ea, &ts, 0);
573}
574
54929fd3 575printfrr_ext_autoreg_p("TT", printfrr_tt);
2c76ba43
DL
576static ssize_t printfrr_tt(struct fbuf *buf, struct printfrr_eargs *ea,
577 const void *vptr)
578{
579 const time_t *tt = vptr;
580 struct timespec ts;
581
582 if (!tt)
782fe5e4 583 return printfrr_time(buf, ea, NULL, TIMEFMT_SECONDS);
2c76ba43
DL
584
585 ts.tv_sec = *tt;
586 ts.tv_nsec = 0;
587 return printfrr_time(buf, ea, &ts, TIMEFMT_SECONDS);
588}