]> git.proxmox.com Git - mirror_frr.git/blob - lib/strformat.c
*: use semicolon after printfrr_ext_autoreg_{p,d}
[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
365 if (flags & TIMEFMT_REALTIME)
366 *real_ts = *ts;
367 else if (flags & TIMEFMT_MONOTONIC) {
368 struct timespec mono_now[1];
369
370 clock_gettime(CLOCK_REALTIME, real_ts);
371 clock_gettime(CLOCK_MONOTONIC, mono_now);
372
373 timespecsub(real_ts, mono_now, real_ts);
374 timespecadd(real_ts, ts, real_ts);
375 } else {
376 clock_gettime(CLOCK_REALTIME, real_ts);
377
378 if (flags & TIMEFMT_SINCE)
379 timespecsub(real_ts, ts, real_ts);
380 else /* flags & TIMEFMT_UNTIL */
381 timespecadd(real_ts, ts, real_ts);
382 }
383
384 localtime_r(&real_ts->tv_sec, &tm);
385
386 if (flags & TIMEFMT_ISO8601) {
387 if (flags & TIMEFMT_SPACE)
388 strftime(cbuf, sizeof(cbuf), "%Y-%m-%d %H:%M:%S", &tm);
389 else
390 strftime(cbuf, sizeof(cbuf), "%Y-%m-%dT%H:%M:%S", &tm);
391 ret += bputs(buf, cbuf);
392
393 if (precision == -1)
394 precision = 3;
395 ret += do_subsec(buf, real_ts, precision, flags);
396 } else {
397 size_t len;
398
399 asctime_r(&tm, cbuf);
400
401 len = strlen(cbuf);
402 if (!len)
403 /* WTF. */
404 return 0;
405 if (cbuf[len - 1] == '\n')
406 cbuf[len - 1] = '\0';
407
408 ret += bputs(buf, cbuf);
409 }
410 return ret;
411 }
412
413 static ssize_t printfrr_reltime(struct fbuf *buf, struct printfrr_eargs *ea,
414 const struct timespec *ts, unsigned int flags)
415 {
416 struct timespec real_ts[1];
417 ssize_t ret = 0;
418 const char *space = "";
419 const char *dashes = "-";
420 int precision = ea->precision;
421
422 while (ea->fmt[0]) {
423 char ch = *ea->fmt++;
424
425 switch (ch) {
426 case 'p':
427 flags |= TIMEFMT_SPACE;
428 space = " ";
429 continue;
430 case 't':
431 flags |= TIMEFMT_BASIC;
432 continue;
433 case 'd':
434 flags |= TIMEFMT_DECIMAL;
435 continue;
436 case 'm':
437 flags |= TIMEFMT_MMSS;
438 dashes = "--:--";
439 continue;
440 case 'h':
441 flags |= TIMEFMT_HHMMSS;
442 dashes = "--:--:--";
443 continue;
444 case 'x':
445 flags |= TIMEFMT_DASHES;
446 continue;
447 }
448
449 ea->fmt--;
450 break;
451 }
452
453 if (flags & TIMEFMT_SKIP)
454 return 0;
455
456 if (flags & TIMEFMT_ABSOLUTE) {
457 struct timespec anchor[1];
458
459 if (flags & TIMEFMT_REALTIME)
460 clock_gettime(CLOCK_REALTIME, anchor);
461 else
462 clock_gettime(CLOCK_MONOTONIC, anchor);
463 if (flags & TIMEFMT_UNTIL)
464 timespecsub(ts, anchor, real_ts);
465 else /* flags & TIMEFMT_SINCE */
466 timespecsub(anchor, ts, real_ts);
467 } else
468 *real_ts = *ts;
469
470 if (real_ts->tv_sec == 0 && real_ts->tv_nsec == 0 &&
471 (flags & TIMEFMT_DASHES))
472 return bputs(buf, dashes);
473
474 if (real_ts->tv_sec < 0) {
475 if (flags & TIMEFMT_DASHES)
476 return bputs(buf, dashes);
477
478 /* -0.3s is { -1s + 700ms } */
479 real_ts->tv_sec = -real_ts->tv_sec - 1;
480 real_ts->tv_nsec = 1000000000L - real_ts->tv_nsec;
481 if (real_ts->tv_nsec >= 1000000000L) {
482 real_ts->tv_sec++;
483 real_ts->tv_nsec -= 1000000000L;
484 }
485
486 /* all formats have a - make sense in front */
487 ret += bputch(buf, '-');
488 }
489
490 if (flags & TIMEFMT_DECIMAL) {
491 ret += bprintfrr(buf, "%lld", (long long)real_ts->tv_sec);
492 if (precision == -1)
493 precision = 3;
494 ret += do_subsec(buf, real_ts, precision, flags);
495 return ret;
496 }
497
498 /* these divisions may be slow on embedded boxes, hence only do the
499 * ones we need, plus the ?: zero check to hopefully skip zeros fast
500 */
501 lldiv_t min_sec = lldiv(real_ts->tv_sec, 60);
502
503 if (flags & TIMEFMT_MMSS) {
504 ret += bprintfrr(buf, "%02lld:%02lld", min_sec.quot,
505 min_sec.rem);
506 ret += do_subsec(buf, real_ts, precision, flags);
507 return ret;
508 }
509
510 lldiv_t hour_min = min_sec.quot ? lldiv(min_sec.quot, 60) : (lldiv_t){};
511
512 if (flags & TIMEFMT_HHMMSS) {
513 ret += bprintfrr(buf, "%02lld:%02lld:%02lld", hour_min.quot,
514 hour_min.rem, min_sec.rem);
515 ret += do_subsec(buf, real_ts, precision, flags);
516 return ret;
517 }
518
519 lldiv_t day_hour =
520 hour_min.quot ? lldiv(hour_min.quot, 24) : (lldiv_t){};
521 lldiv_t week_day =
522 day_hour.quot ? lldiv(day_hour.quot, 7) : (lldiv_t){};
523
524 /* if sub-second precision is not supported, return */
525 if (flags & TIMEFMT_BASIC) {
526 /* match frrtime_to_interval (without space flag) */
527 if (week_day.quot)
528 ret += bprintfrr(buf, "%lldw%s%lldd%s%02lldh",
529 week_day.quot, space, week_day.rem,
530 space, day_hour.rem);
531 else if (day_hour.quot)
532 ret += bprintfrr(buf, "%lldd%s%02lldh%s%02lldm",
533 day_hour.quot, space, day_hour.rem,
534 space, hour_min.rem);
535 else
536 ret += bprintfrr(buf, "%02lld:%02lld:%02lld",
537 hour_min.quot, hour_min.rem,
538 min_sec.rem);
539 /* no sub-seconds here */
540 return ret;
541 }
542
543 /* default format */
544 if (week_day.quot)
545 ret += bprintfrr(buf, "%lldw%s", week_day.quot, space);
546 if (week_day.rem || week_day.quot)
547 ret += bprintfrr(buf, "%lldd%s", week_day.rem, space);
548
549 ret += bprintfrr(buf, "%02lld:%02lld:%02lld", day_hour.rem,
550 hour_min.rem, min_sec.rem);
551
552 if (precision == -1)
553 precision = 3;
554 ret += do_subsec(buf, real_ts, precision, flags);
555 return ret;
556 }
557
558 printfrr_ext_autoreg_p("TS", printfrr_ts);
559 static ssize_t printfrr_ts(struct fbuf *buf, struct printfrr_eargs *ea,
560 const void *vptr)
561 {
562 const struct timespec *ts = vptr;
563
564 if (!ts)
565 return bputs(buf, "(null)");
566 return printfrr_time(buf, ea, ts, 0);
567 }
568
569 printfrr_ext_autoreg_p("TV", printfrr_tv);
570 static ssize_t printfrr_tv(struct fbuf *buf, struct printfrr_eargs *ea,
571 const void *vptr)
572 {
573 const struct timeval *tv = vptr;
574 struct timespec ts;
575
576 if (!tv)
577 return bputs(buf, "(null)");
578
579 ts.tv_sec = tv->tv_sec;
580 ts.tv_nsec = tv->tv_usec * 1000;
581 return printfrr_time(buf, ea, &ts, 0);
582 }
583
584 printfrr_ext_autoreg_p("TT", printfrr_tt);
585 static ssize_t printfrr_tt(struct fbuf *buf, struct printfrr_eargs *ea,
586 const void *vptr)
587 {
588 const time_t *tt = vptr;
589 struct timespec ts;
590
591 if (!tt)
592 return bputs(buf, "(null)");
593
594 ts.tv_sec = *tt;
595 ts.tv_nsec = 0;
596 return printfrr_time(buf, ea, &ts, TIMEFMT_SECONDS);
597 }