Coverage Report

Created: 2025-10-23 06:55

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