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