summaryrefslogtreecommitdiffstats
path: root/lib/printfrr.h
blob: b8bef562d25c6fc1c775f45e1800634470023b66 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
// SPDX-License-Identifier: ISC
/*
 * Copyright (c) 2019  David Lamparter, for NetDEF, Inc.
 */

#ifndef _FRR_PRINTFRR_H
#define _FRR_PRINTFRR_H

#include <stddef.h>
#include <stdarg.h>
#include <stdint.h>

#include "compiler.h"
#include "memory.h"

#ifdef __cplusplus
extern "C" {
#endif

struct fmt_outpos {
	unsigned int off_start, off_end;
};

struct fbuf {
	char *buf;
	char *pos;
	size_t len;

	struct fmt_outpos *outpos;
	size_t outpos_n, outpos_i;
};

#define at(a, b) PRINTFRR(a, b)
#define atn(a, b) \
	at(a, b) __attribute__((nonnull(1) _RET_NONNULL))
#define atm(a, b) \
	atn(a, b) __attribute__((malloc))

/* return value is length needed for the full string (excluding \0) in all
 * cases.  The functions write as much as they can, but continue regardless,
 * so the return value is independent of buffer length.  Both bprintfrr and
 * snprintf also accept NULL as output buffer.
 */

/* bprintfrr does NOT null terminate! use sparingly (only provided since it's
 * the most direct interface) - useful for incrementally building long text
 * (call bprintfrr repeatedly with the same buffer)
 */
ssize_t vbprintfrr(struct fbuf *out, const char *fmt, va_list) at(2, 0);
ssize_t  bprintfrr(struct fbuf *out, const char *fmt, ...)     at(2, 3);

/* these do null terminate like their snprintf cousins */
ssize_t vsnprintfrr(char *out, size_t sz, const char *fmt, va_list) at(3, 0);
ssize_t  snprintfrr(char *out, size_t sz, const char *fmt, ...)     at(3, 4);

/* c = continue / concatenate (append at the end of the string)
 * return value is would-be string length (regardless of buffer length),
 * i.e. includes already written chars */
ssize_t vcsnprintfrr(char *out, size_t sz, const char *fmt, va_list) at(3, 0);
ssize_t  csnprintfrr(char *out, size_t sz, const char *fmt, ...)     at(3, 4);

/* memory allocations don't fail in FRR, so you always get something here.
 * (in case of error, returns a strdup of the format string) */
char *vasprintfrr(struct memtype *mt, const char *fmt, va_list) atm(2, 0);
char  *asprintfrr(struct memtype *mt, const char *fmt, ...)     atm(2, 3);

/* try to use provided buffer (presumably from stack), allocate if it's too
 * short.  Must call XFREE(mt, return value) if return value != out.
 */
char *vasnprintfrr(struct memtype *mt, char *out, size_t sz,
		   const char *fmt, va_list) atn(4, 0);
char  *asnprintfrr(struct memtype *mt, char *out, size_t sz,
		   const char *fmt, ...)     atn(4, 5);

#define printfrr(fmt, ...)                                                     \
	do {                                                                   \
		char buf[256], *out;                                           \
		out = asnprintfrr(MTYPE_TMP, buf, sizeof(buf), fmt,            \
				  ##__VA_ARGS__);                              \
		fputs(out, stdout);                                            \
		if (out != buf)                                                \
			XFREE(MTYPE_TMP, out);                                 \
	} while (0)

#undef at
#undef atm
#undef atn

/* extension specs must start with a capital letter (this is a restriction
 * for both performance's and human understanding's sake.)
 *
 * Note that the entire thing mostly works because a letter directly following
 * a %p print specifier is extremely unlikely to occur (why would you want to
 * print "0x12345678HELLO"?)  Normally, you'd expect spacing or punctuation
 * after a placeholder.  That also means that neither of those works well for
 * extension purposes, e.g. "%p{foo}" is reasonable to see actually used.
 *
 * TODO: would be nice to support a "%pF%dF" specifier that consumes 2
 * arguments, e.g. to pass an integer + a list of known values...  can be
 * done, but a bit tricky.
 */
#define printfrr_ext_char(ch) ((ch) >= 'A' && (ch) <= 'Z')

struct printfrr_eargs;

struct printfrr_ext {
	/* embedded string to minimize cache line pollution */
	char match[8];

	/* both can be given, if not the code continues searching
	 * (you can do %pX and %dX in 2 different entries)
	 *
	 * return value: number of bytes that would be printed if the buffer
	 * was large enough.  be careful about not under-reporting this;
	 * otherwise asnprintf() & co. will get broken.  Returning -1 means
	 * something went wrong & default %p/%d handling should be executed.
	 *
	 * to consume extra input flags after %pXY, increment *fmt.  It points
	 * at the first character after %pXY at entry.  Convention is to make
	 * those flags lowercase letters or numbers.
	 */
	ssize_t (*print_ptr)(struct fbuf *buf, struct printfrr_eargs *info,
			     const void *);
	ssize_t (*print_int)(struct fbuf *buf, struct printfrr_eargs *info,
			     uintmax_t);
};

/* additional information passed to extended formatters */

struct printfrr_eargs {
	/* position in the format string.  Points to directly after the
	 * extension specifier.  Increment when consuming extra "flag
	 * characters".
	 */
	const char *fmt;

	/* %.1234x / %.*x
	 * not POSIX compatible when used with %p, will cause warnings from
	 * GCC & clang.  Usable with %d.  Not used by the printfrr() itself
	 * for extension specifiers, so essentially available as a "free"
	 * parameter.  -1 if not specified.  Value in the format string
	 * cannot be negative, but negative values can be passed with %.*x
	 */
	int precision;

	/* %1234x / %*x
	 * regular width specification.  Internally handled by printfrr(), set
	 * to 0 if consumed by the extension in order to suppress standard
	 * width/padding behavior.  0 if not specified.
	 *
	 * NB: always positive, even if a negative value is passed in with
	 * %*x.  (The sign is used for the - flag.)
	 */
	int width;

	/* %#x
	 * "alternate representation" flag, not POSIX compatible when used
	 * with %p or %d, will cause warnings from GCC & clang.  Not used by
	 * printfrr() itself for extension specifiers.
	 */
	bool alt_repr;

	/* %-x
	 * left-pad flag.  Internally handled by printfrr() if width is
	 * nonzero.  Only use if the extension sets width to 0.
	 */
	bool leftadj;
};

/* for any extension that needs a buffer length */

static inline ssize_t printfrr_ext_len(struct printfrr_eargs *ea)
{
	ssize_t rv;

	if (ea->precision >= 0)
		rv = ea->precision;
	else if (ea->width >= 0) {
		rv = ea->width;
		ea->width = -1;
	} else
		rv = -1;

	return rv;
}

/* no locking - must be called when single threaded (e.g. at startup.)
 * this restriction hopefully won't be a huge bother considering normal usage
 * scenarios...
 */
void printfrr_ext_reg(const struct printfrr_ext *);

#define printfrr_ext_autoreg_p(matchs, print_fn)                               \
	static ssize_t print_fn(struct fbuf *, struct printfrr_eargs *,        \
				const void *);                                 \
	static const struct printfrr_ext _printext_##print_fn = {              \
		.match = matchs,                                               \
		.print_ptr = print_fn,                                         \
	};                                                                     \
	static void _printreg_##print_fn(void) __attribute__((constructor));   \
	static void _printreg_##print_fn(void)                                 \
	{                                                                      \
		printfrr_ext_reg(&_printext_##print_fn);                       \
	}                                                                      \
	MACRO_REQUIRE_SEMICOLON()

#define printfrr_ext_autoreg_i(matchs, print_fn)                               \
	static ssize_t print_fn(struct fbuf *, struct printfrr_eargs *,        \
				uintmax_t);                                    \
	static const struct printfrr_ext _printext_##print_fn = {              \
		.match = matchs,                                               \
		.print_int = print_fn,                                         \
	};                                                                     \
	static void _printreg_##print_fn(void) __attribute__((constructor));   \
	static void _printreg_##print_fn(void)                                 \
	{                                                                      \
		printfrr_ext_reg(&_printext_##print_fn);                       \
	}                                                                      \
	MACRO_REQUIRE_SEMICOLON()

/* fbuf helper functions - note all 3 of these return the length that would
 * be written regardless of how much space was available in the buffer, as
 * needed for implementing printfrr extensions.  (They also accept NULL buf
 * for that.)
 */

static inline ssize_t bputs(struct fbuf *buf, const char *str)
{
	size_t len = strlen(str);
	size_t ncopy;

	if (!buf)
		return len;

	ncopy = MIN(len, (size_t)(buf->buf + buf->len - buf->pos));
	memcpy(buf->pos, str, ncopy);
	buf->pos += ncopy;

	return len;
}

static inline ssize_t bputch(struct fbuf *buf, char ch)
{
	if (buf && buf->pos < buf->buf + buf->len)
		*buf->pos++ = ch;
	return 1;
}

static inline ssize_t bputhex(struct fbuf *buf, uint8_t val)
{
	static const char hexch[] = "0123456789abcdef";

	if (buf && buf->pos < buf->buf + buf->len)
		*buf->pos++ = hexch[(val >> 4) & 0xf];
	if (buf && buf->pos < buf->buf + buf->len)
		*buf->pos++ = hexch[val & 0xf];
	return 2;
}

/* %pVA extension, equivalent to Linux kernel %pV */

struct va_format {
	const char *fmt;
	va_list *va;
};

#ifdef _FRR_ATTRIBUTE_PRINTFRR
#pragma FRR printfrr_ext "%pFB" (struct fbuf *)
#pragma FRR printfrr_ext "%pVA" (struct va_format *)

#pragma FRR printfrr_ext "%pHX" (signed char *)
#pragma FRR printfrr_ext "%pHX" (unsigned char *)
#pragma FRR printfrr_ext "%pHX" (void *)
#pragma FRR printfrr_ext "%pHS" (signed char *)
#pragma FRR printfrr_ext "%pHS" (unsigned char *)
#pragma FRR printfrr_ext "%pHS" (void *)

#pragma FRR printfrr_ext "%pSE" (char *)
#pragma FRR printfrr_ext "%pSQ" (char *)

#pragma FRR printfrr_ext "%pTS" (struct timespec *)
#pragma FRR printfrr_ext "%pTV" (struct timeval *)
#pragma FRR printfrr_ext "%pTT" (time_t *)
#endif

/* when using non-ISO-C compatible extension specifiers... */

#ifdef _FRR_ATTRIBUTE_PRINTFRR
#define FMT_NSTD_BEGIN
#define FMT_NSTD_END
#else /* !_FRR_ATTRIBUTE_PRINTFRR */
#define FMT_NSTD_BEGIN \
	_Pragma("GCC diagnostic push")                                         \
	_Pragma("GCC diagnostic ignored \"-Wformat\"")                         \
	/* end */
#define FMT_NSTD_END \
	_Pragma("GCC diagnostic pop")                                          \
	/* end */
#endif

#define FMT_NSTD(expr)                                                         \
	({                                                                     \
		FMT_NSTD_BEGIN                                                 \
		typeof(expr) _v;                                               \
		_v = expr;                                                     \
		FMT_NSTD_END                                                   \
		_v;                                                            \
	})                                                                     \
	/* end */

#ifdef __cplusplus
}
#endif

#endif