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