]>
Commit | Line | Data |
---|---|---|
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 | 19 | printfrr_ext_autoreg_p("HX", printfrr_hexdump); |
a4cb97a6 DL |
20 | static 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 | 50 | printfrr_ext_autoreg_p("HS", printfrr_hexdstr); |
a4cb97a6 DL |
51 | static 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 | |
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 | { | |
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 | 193 | printfrr_ext_autoreg_p("SE", printfrr_escape); |
7798203f DL |
194 | static 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 | 218 | printfrr_ext_autoreg_p("SQ", printfrr_quote); |
7798203f DL |
219 | static 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 | |
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 | { | |
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 | ||
310 | static 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 | ||
326 | static 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 | ||
404 | static 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 | 551 | printfrr_ext_autoreg_p("TS", printfrr_ts); |
2c76ba43 DL |
552 | static 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 | 560 | printfrr_ext_autoreg_p("TV", printfrr_tv); |
2c76ba43 DL |
561 | static 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 | 575 | printfrr_ext_autoreg_p("TT", printfrr_tt); |
2c76ba43 DL |
576 | static 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 | } |