1 /*-
2  * Copyright 2016 Vsevolod Stakhov
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /* Copyright (C) 2002-2015 Igor Sysoev
18  * Copyright (C) 2011-2015 Nginx, Inc.
19  * All rights reserved.
20  *
21  * Redistribution and use in source and binary forms, with or without
22  * modification, are permitted provided that the following conditions are met:
23  *       * Redistributions of source code must retain the above copyright
24  *         notice, this list of conditions and the following disclaimer.
25  *       * Redistributions in binary form must reproduce the above copyright
26  *         notice, this list of conditions and the following disclaimer in the
27  *         documentation and/or other materials provided with the distribution.
28  *
29  * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
30  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
31  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
32  * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
33  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
35  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
36  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
38  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39  */
40 
41 #include "printf.h"
42 #include "str_util.h"
43 #include "contrib/fpconv/fpconv.h"
44 
45 /**
46  * From FreeBSD libutil code
47  */
48 static const int maxscale = 6;
49 static const gchar _hex[] = "0123456789abcdef";
50 static const gchar _HEX[] = "0123456789ABCDEF";
51 
52 static gchar *
rspamd_humanize_number(gchar * buf,gchar * last,gint64 num,gboolean bytes)53 rspamd_humanize_number (gchar *buf, gchar *last, gint64 num, gboolean bytes)
54 {
55 	const gchar *prefixes;
56 	int i, r, remainder, sign;
57 	gint64 divisor;
58 	gsize len = last - buf;
59 
60 	remainder = 0;
61 
62 	if (!bytes) {
63 		divisor = 1000;
64 		prefixes = "\0\0\0\0k\0\0\0M\0\0\0G\0\0\0T\0\0\0P\0\0\0E";
65 	}
66 	else {
67 		divisor = 1024;
68 		prefixes = "B\0\0\0KiB\0MiB\0GiB\0TiB\0PiB\0EiB";
69 	}
70 
71 #define SCALE2PREFIX(scale)     (&prefixes[(scale) * 4])
72 
73 	if (num < 0) {
74 		sign = -1;
75 		num = -num;
76 	}
77 	else {
78 		sign = 1;
79 	}
80 
81 	/*
82 	 * Divide the number until it fits the given column.
83 	 * If there will be an overflow by the rounding below,
84 	 * divide once more.
85 	 */
86 	for (i = 0; i < maxscale && num > divisor; i++) {
87 		remainder = num % divisor;
88 		num /= divisor;
89 	}
90 
91 	if (remainder == 0 || num > divisor / 2) {
92 		r = rspamd_snprintf (buf, len, "%L%s",
93 				sign * (num + (remainder + 50) / divisor),
94 				SCALE2PREFIX (i));
95 	}
96 	else {
97 		/* Floating point version */
98 		r = rspamd_snprintf (buf, len, "%.2f%s",
99 				sign * (num + remainder / (gdouble)divisor),
100 				SCALE2PREFIX (i));
101 	}
102 
103 #undef SCALE2PREFIX
104 
105 	return buf + r;
106 }
107 
108 
109 static inline unsigned
rspamd_decimal_digits32(guint32 val)110 rspamd_decimal_digits32 (guint32 val)
111 {
112 	static const guint32 powers_of_10[] = {
113 			0,
114 			10,
115 			100,
116 			1000,
117 			10000,
118 			100000,
119 			1000000,
120 			10000000,
121 			100000000,
122 			1000000000
123 	};
124 	unsigned tmp;
125 
126 #if defined(_MSC_VER)
127 	unsigned long r = 0;
128 	_BitScanReverse (&r, val | 1);
129 	tmp = (r + 1) * 1233 >> 12;
130 #elif defined(__GNUC__) && (__GNUC__ >= 3)
131 	tmp = (32 - __builtin_clz (val | 1U)) * 1233 >> 12;
132 
133 #else /* Software version */
134 	static const unsigned debruijn_tbl[32] = { 0,  9,  1, 10, 13, 21,  2, 29,
135 											   11, 14, 16, 18, 22, 25,  3, 30,
136 											   8, 12, 20, 28, 15, 17, 24,  7,
137 											   19, 27, 23,  6, 26,  5,  4, 31 };
138 	guint32 v = val | 1;
139 
140 	v |= v >> 1;
141 	v |= v >> 2;
142 	v |= v >> 4;
143 	v |= v >> 8;
144 	v |= v >> 16;
145 	tmp = (1 + debruijn_tbl[(v * 0x07C4ACDDU) >> 27]) * 1233 >> 12;
146 #endif
147 	return tmp - (val < powers_of_10[tmp]) + 1;
148 }
149 
150 static inline unsigned
rspamd_decimal_digits64(guint64 val)151 rspamd_decimal_digits64 (guint64 val)
152 {
153 	static const guint64 powers_of_10[] = {
154 			0,
155 			10ULL,
156 			100ULL,
157 			1000ULL,
158 			10000ULL,
159 			100000ULL,
160 			1000000ULL,
161 			10000000ULL,
162 			100000000ULL,
163 			1000000000ULL,
164 			10000000000ULL,
165 			100000000000ULL,
166 			1000000000000ULL,
167 			10000000000000ULL,
168 			100000000000000ULL,
169 			1000000000000000ULL,
170 			10000000000000000ULL,
171 			100000000000000000ULL,
172 			1000000000000000000ULL,
173 			10000000000000000000ULL
174 	};
175 	unsigned tmp;
176 
177 #if defined(_MSC_VER)
178 #if _M_IX86
179 	unsigned long r = 0;
180 	guint64 m = val | 1;
181 	if (_BitScanReverse (&r, m >> 32)) {
182 		r += 32;
183 	}
184 	else {
185 		_BitScanReverse (&r, m & 0xFFFFFFFF);
186 	}
187 	tmp = (r + 1) * 1233 >> 12;
188 #else
189 	unsigned long r = 0;
190 	_BitScanReverse64 (&r, val | 1);
191 	tmp = (r + 1) * 1233 >> 12;
192 #endif
193 #elif defined(__GNUC__) && (__GNUC__ >= 3)
194 	tmp = (64 - __builtin_clzll (val | 1ULL)) * 1233 >> 12;
195 #else /* Software version */
196 	static const unsigned debruijn_tbl[32] = { 0,  9,  1, 10, 13, 21,  2, 29,
197 											   11, 14, 16, 18, 22, 25,  3, 30,
198 											   8, 12, 20, 28, 15, 17, 24,  7,
199 											   19, 27, 23,  6, 26,  5,  4, 31 };
200 	guint32 v = val >> 32;
201 
202 	if (v) {
203 		v |= 1;
204 		v |= v >> 1;
205 		v |= v >> 2;
206 		v |= v >> 4;
207 		v |= v >> 8;
208 		v |= v >> 16;
209 		tmp = 32 + debruijn_tbl[(v * 0x07C4ACDDU) >> 27];
210 	}
211 	else {
212 		v = val & 0xFFFFFFFF;
213 		v |= 1;
214 		v |= v >> 1;
215 		v |= v >> 2;
216 		v |= v >> 4;
217 		v |= v >> 8;
218 		v |= v >> 16;
219 
220 		tmp = debruijn_tbl[(v * 0x07C4ACDDU) >> 27];
221 	}
222 
223 
224 	tmp = (tmp + 1) * 1233 >> 12;
225 #endif
226 
227 	return tmp - (val < powers_of_10[tmp]) + 1;
228 }
229 
230 /*
231  * Idea from https://github.com/miloyip/itoa-benchmark:
232  * Uses lookup table (LUT) of digit pairs for division/modulo of 100.
233  *
234  * Mentioned in:
235  * https://www.slideshare.net/andreialexandrescu1/three-optimization-tips-for-c-15708507
236  */
237 
238 static const char int_lookup_table[200] = {
239 		'0','0','0','1','0','2','0','3','0','4',
240 		'0','5','0','6','0','7','0','8','0','9',
241 		'1','0','1','1','1','2','1','3','1','4',
242 		'1','5','1','6','1','7','1','8','1','9',
243 		'2','0','2','1','2','2','2','3','2','4',
244 		'2','5','2','6','2','7','2','8','2','9',
245 		'3','0','3','1','3','2','3','3','3','4',
246 		'3','5','3','6','3','7','3','8','3','9',
247 		'4','0','4','1','4','2','4','3','4','4',
248 		'4','5','4','6','4','7','4','8','4','9',
249 		'5','0','5','1','5','2','5','3','5','4',
250 		'5','5','5','6','5','7','5','8','5','9',
251 		'6','0','6','1','6','2','6','3','6','4',
252 		'6','5','6','6','6','7','6','8','6','9',
253 		'7','0','7','1','7','2','7','3','7','4',
254 		'7','5','7','6','7','7','7','8','7','9',
255 		'8','0','8','1','8','2','8','3','8','4',
256 		'8','5','8','6','8','7','8','8','8','9',
257 		'9','0','9','1','9','2','9','3','9','4',
258 		'9','5','9','6','9','7','9','8','9','9'
259 };
260 
261 static inline guint
rspamd_uint32_print(guint32 in,gchar * out)262 rspamd_uint32_print (guint32 in, gchar *out)
263 {
264 	guint ndigits = rspamd_decimal_digits32 (in);
265 	gchar *p;
266 
267 	p = out + ndigits - 1;
268 
269 	while (in >= 100) {
270 		unsigned idx = (in % 100) * 2;
271 
272 		/* Do two digits at once */
273 		*p-- = int_lookup_table[idx + 1];
274 		*p-- = int_lookup_table[idx];
275 
276 		in /= 100;
277 	}
278 
279 	if (in < 10) {
280 		*p = ((char)in) + '0';
281 	}
282 	else {
283 		unsigned idx = in * 2;
284 
285 		*p-- = int_lookup_table[idx + 1];
286 		*p = int_lookup_table[idx];
287 	}
288 
289 	return ndigits;
290 }
291 
292 static inline guint
rspamd_uint64_print(guint64 in,gchar * out)293 rspamd_uint64_print (guint64 in, gchar *out)
294 {
295 	guint ndigits = rspamd_decimal_digits64 (in);
296 	guint32 v32;
297 	gchar *p;
298 
299 	p = out + ndigits - 1;
300 
301 	while (in >= 100000000) {
302 		v32 = (guint32)(in % 100000000);
303 		guint32 a, b, a1, a2, b1, b2;
304 
305 		/* Initial spill */
306 		a = v32 / 10000;
307 		b = v32 % 10000;
308 		a1 = (a / 100) * 2;
309 		a2 = (a % 100) * 2;
310 		b1 = (b / 100) * 2;
311 		b2 = (b % 100) * 2;
312 
313 		/* Fill 8 digits at once */
314 		*p-- = int_lookup_table[b2 + 1];
315 		*p-- = int_lookup_table[b2];
316 		*p-- = int_lookup_table[b1 + 1];
317 		*p-- = int_lookup_table[b1];
318 		*p-- = int_lookup_table[a2 + 1];
319 		*p-- = int_lookup_table[a2];
320 		*p-- = int_lookup_table[a1 + 1];
321 		*p-- = int_lookup_table[a1];
322 
323 		in /= 100000000;
324 	}
325 
326 	/* Remaining 32 bit */
327 	v32 = (guint32)in;
328 
329 	while (v32 >= 100) {
330 		unsigned idx = (v32 % 100) << 1;
331 
332 		/* Do 2 digits at once */
333 		*p-- = int_lookup_table[idx + 1];
334 		*p-- = int_lookup_table[idx];
335 
336 		v32 /= 100;
337 	}
338 
339 	if (v32 < 10) {
340 		*p = ((char)v32) + '0';
341 	}
342 	else {
343 		unsigned idx = v32 * 2;
344 
345 		*p-- = int_lookup_table[idx + 1];
346 		*p = int_lookup_table[idx];
347 	}
348 
349 	return ndigits;
350 }
351 
352 static gchar *
rspamd_sprintf_num(gchar * buf,gchar * last,guint64 ui64,gchar zero,guint hexadecimal,guint width)353 rspamd_sprintf_num (gchar *buf, gchar *last, guint64 ui64, gchar zero,
354 					  guint hexadecimal, guint width)
355 {
356 	gchar *p, temp[sizeof ("18446744073709551615")];
357 	size_t len;
358 
359 	if (hexadecimal == 0) {
360 		p = temp;
361 
362 		if (ui64 < G_MAXUINT32) {
363 			len = rspamd_uint32_print ((guint32)ui64, temp);
364 		}
365 		else {
366 			len = rspamd_uint64_print (ui64, temp);
367 		}
368 	}
369 	else if (hexadecimal == 1) {
370 		p = temp + sizeof(temp);
371 		do {
372 			*--p = _hex[(guint32) (ui64 & 0xf)];
373 		} while (ui64 >>= 4);
374 
375 		len = (temp + sizeof (temp)) - p;
376 	}
377 	else { /* hexadecimal == 2 */
378 		p = temp + sizeof(temp);
379 		do {
380 			*--p = _HEX[(guint32) (ui64 & 0xf)];
381 		} while (ui64 >>= 4);
382 
383 		len = (temp + sizeof (temp)) - p;
384 	}
385 
386 	/* zero or space padding */
387 
388 	if (len < width) {
389 		width -= len;
390 
391 		while (width-- > 0 && buf < last) {
392 			*buf++ = zero;
393 		}
394 	}
395 
396 	/* number safe copy */
397 
398 	if (buf + len > last) {
399 		len = last - buf;
400 	}
401 
402 	return ((gchar *)memcpy (buf, p, len)) + len;
403 }
404 
405 struct rspamd_printf_char_buf {
406 	char *begin;
407 	char *pos;
408 	glong remain;
409 };
410 
411 static glong
rspamd_printf_append_char(const gchar * buf,glong buflen,gpointer ud)412 rspamd_printf_append_char (const gchar *buf, glong buflen, gpointer ud)
413 {
414 	struct rspamd_printf_char_buf *dst = (struct rspamd_printf_char_buf *)ud;
415 	glong wr;
416 
417 	if (dst->remain <= 0) {
418 		return dst->remain;
419 	}
420 
421 	wr = MIN (dst->remain, buflen);
422 	memcpy (dst->pos, buf, wr);
423 	dst->remain -= wr;
424 	dst->pos += wr;
425 
426 	return wr;
427 }
428 
429 static glong
rspamd_printf_append_file(const gchar * buf,glong buflen,gpointer ud)430 rspamd_printf_append_file (const gchar *buf, glong buflen, gpointer ud)
431 {
432 	FILE *dst = (FILE *)ud;
433 	if (buflen > 0) {
434 		return fwrite (buf, 1, buflen, dst);
435 	}
436 	else {
437 		return 0;
438 	}
439 }
440 
441 static glong
rspamd_printf_append_gstring(const gchar * buf,glong buflen,gpointer ud)442 rspamd_printf_append_gstring (const gchar *buf, glong buflen, gpointer ud)
443 {
444 	GString *dst = (GString *)ud;
445 
446 	if (buflen > 0) {
447 		g_string_append_len (dst, buf, buflen);
448 	}
449 
450 	return buflen;
451 }
452 
453 static glong
rspamd_printf_append_fstring(const gchar * buf,glong buflen,gpointer ud)454 rspamd_printf_append_fstring (const gchar *buf, glong buflen, gpointer ud)
455 {
456 	rspamd_fstring_t **dst = ud;
457 
458 	if (buflen > 0) {
459 		*dst = rspamd_fstring_append (*dst, buf, buflen);
460 	}
461 
462 	return buflen;
463 }
464 
465 glong
rspamd_fprintf(FILE * f,const gchar * fmt,...)466 rspamd_fprintf (FILE *f, const gchar *fmt, ...)
467 {
468 	va_list args;
469 	glong r;
470 
471 	va_start (args, fmt);
472 	r = rspamd_vprintf_common (rspamd_printf_append_file, f, fmt, args);
473 	va_end (args);
474 
475 	return r;
476 }
477 
478 glong
rspamd_printf(const gchar * fmt,...)479 rspamd_printf (const gchar *fmt, ...)
480 {
481 	va_list args;
482 	glong r;
483 
484 	va_start (args, fmt);
485 	r = rspamd_vprintf_common (rspamd_printf_append_file, stdout, fmt, args);
486 	va_end (args);
487 
488 	return r;
489 }
490 
491 glong
rspamd_log_fprintf(FILE * f,const gchar * fmt,...)492 rspamd_log_fprintf (FILE *f, const gchar *fmt, ...)
493 {
494 	va_list args;
495 	glong r;
496 
497 	va_start (args, fmt);
498 	r = rspamd_vprintf_common (rspamd_printf_append_file, f, fmt, args);
499 	va_end (args);
500 
501 	fflush (f);
502 
503 	return r;
504 }
505 
506 
507 glong
rspamd_snprintf(gchar * buf,glong max,const gchar * fmt,...)508 rspamd_snprintf (gchar *buf, glong max, const gchar *fmt, ...)
509 {
510 	gchar *r;
511 	va_list args;
512 
513 	va_start (args, fmt);
514 	r = rspamd_vsnprintf (buf, max, fmt, args);
515 	va_end (args);
516 
517 	return (r - buf);
518 }
519 
520 gchar *
rspamd_vsnprintf(gchar * buf,glong max,const gchar * fmt,va_list args)521 rspamd_vsnprintf (gchar *buf, glong max, const gchar *fmt, va_list args)
522 {
523 	struct rspamd_printf_char_buf dst;
524 
525 	dst.begin = buf;
526 	dst.pos = dst.begin;
527 	dst.remain = max - 1;
528 	(void)rspamd_vprintf_common (rspamd_printf_append_char, &dst, fmt, args);
529 	*dst.pos = '\0';
530 
531 	return dst.pos;
532 }
533 
534 glong
rspamd_printf_gstring(GString * s,const gchar * fmt,...)535 rspamd_printf_gstring (GString *s, const gchar *fmt, ...)
536 {
537 	va_list args;
538 	glong r;
539 
540 	va_start (args, fmt);
541 	r = rspamd_vprintf_gstring (s, fmt, args);
542 	va_end (args);
543 
544 	return r;
545 }
546 
547 glong
rspamd_vprintf_gstring(GString * s,const gchar * fmt,va_list args)548 rspamd_vprintf_gstring (GString *s, const gchar *fmt, va_list args)
549 {
550 	return rspamd_vprintf_common (rspamd_printf_append_gstring, s, fmt, args);
551 }
552 
553 glong
rspamd_printf_fstring(rspamd_fstring_t ** s,const gchar * fmt,...)554 rspamd_printf_fstring (rspamd_fstring_t **s, const gchar *fmt, ...)
555 {
556 	va_list args;
557 	glong r;
558 
559 	va_start (args, fmt);
560 	r = rspamd_vprintf_fstring (s, fmt, args);
561 	va_end (args);
562 
563 	return r;
564 }
565 
566 glong
rspamd_vprintf_fstring(rspamd_fstring_t ** s,const gchar * fmt,va_list args)567 rspamd_vprintf_fstring (rspamd_fstring_t **s, const gchar *fmt, va_list args)
568 {
569 	return rspamd_vprintf_common (rspamd_printf_append_fstring, s, fmt, args);
570 }
571 
572 #define RSPAMD_PRINTF_APPEND(buf, len)                                         \
573 	do {                                                                       \
574 		RSPAMD_PRINTF_APPEND_BUF(buf, len);                                    \
575 		fmt++;                                                                 \
576 		buf_start = fmt;                                                       \
577 	} while (0)
578 
579 #define RSPAMD_PRINTF_APPEND_BUF(buf, len)                                     \
580 	do {                                                                       \
581 		wr = func ((buf), (len), apd);                                         \
582 		if (wr < (__typeof (wr))(len)) {                                       \
583 			goto oob;                                                          \
584 		}                                                                      \
585 		written += wr;                                                         \
586 	} while (0)
587 
588 glong
rspamd_vprintf_common(rspamd_printf_append_func func,gpointer apd,const gchar * fmt,va_list args)589 rspamd_vprintf_common (rspamd_printf_append_func func,
590 	gpointer apd,
591 	const gchar *fmt,
592 	va_list args)
593 {
594 	gchar zero, numbuf[G_ASCII_DTOSTR_BUF_SIZE], dtoabuf[32], *p, *last;
595 	guchar c;
596 	const gchar *buf_start = fmt;
597 	gint d;
598 	gdouble f;
599 	glong written = 0, wr, slen;
600 	gint64 i64;
601 	guint64 ui64;
602 	guint width, sign, hex, humanize, bytes, frac_width, b32, b64;
603 	rspamd_fstring_t *v;
604 	rspamd_ftok_t *tok;
605 	GString *gs;
606 	GError *err;
607 
608 	while (*fmt) {
609 
610 		/*
611 		 * "buf < last" means that we could copy at least one character:
612 		 * the plain character, "%%", "%c", and minus without the checking
613 		 */
614 
615 		if (*fmt == '%') {
616 
617 			/* Append what we have in buf */
618 			if (fmt > buf_start) {
619 				wr = func (buf_start, fmt - buf_start, apd);
620 				if (wr <= 0) {
621 					goto oob;
622 				}
623 				written += wr;
624 			}
625 
626 			i64 = 0;
627 			ui64 = 0;
628 
629 			zero = (gchar) ((*++fmt == '0') ? '0' : ' ');
630 			width = 0;
631 			sign = 1;
632 			hex = 0;
633 			b32 = 0;
634 			b64 = 0;
635 			bytes = 0;
636 			humanize = 0;
637 			frac_width = 0;
638 			slen = -1;
639 
640 			while (*fmt >= '0' && *fmt <= '9') {
641 				width = width * 10 + *fmt++ - '0';
642 			}
643 
644 
645 			for (;; ) {
646 				switch (*fmt) {
647 
648 				case 'u':
649 					sign = 0;
650 					fmt++;
651 					continue;
652 
653 				case 'm':
654 					fmt++;
655 					continue;
656 
657 				case 'X':
658 					hex = 2;
659 					sign = 0;
660 					fmt++;
661 					continue;
662 
663 				case 'x':
664 					hex = 1;
665 					sign = 0;
666 					fmt++;
667 					continue;
668 				case 'b':
669 					b32 = 1;
670 					sign = 0;
671 					fmt++;
672 					continue;
673 				case 'B':
674 					b64 = 1;
675 					sign = 0;
676 					fmt++;
677 					continue;
678 				case 'H':
679 					humanize = 1;
680 					bytes = 1;
681 					sign = 0;
682 					fmt++;
683 					continue;
684 				case 'h':
685 					humanize = 1;
686 					sign = 0;
687 					fmt++;
688 					continue;
689 				case '.':
690 					fmt++;
691 
692 					if (*fmt == '*') {
693 						d = (gint)va_arg (args, gint);
694 						if (G_UNLIKELY (d < 0)) {
695 							return 0;
696 						}
697 						frac_width = (guint)d;
698 						fmt++;
699 					}
700 					else {
701 						while (*fmt >= '0' && *fmt <= '9') {
702 							frac_width = frac_width * 10 + *fmt++ - '0';
703 						}
704 					}
705 
706 					break;
707 
708 				case '*':
709 					d = (gint)va_arg (args, gint);
710 					if (G_UNLIKELY (d < 0)) {
711 						return 0;
712 					}
713 					slen = (glong)d;
714 					fmt++;
715 					continue;
716 
717 				default:
718 					break;
719 				}
720 
721 				break;
722 			}
723 
724 
725 			switch (*fmt) {
726 
727 			case 'V':
728 				v = va_arg (args, rspamd_fstring_t *);
729 
730 				if (v) {
731 					slen = v->len;
732 
733 					if (G_UNLIKELY (width != 0)) {
734 						slen = MIN (v->len, width);
735 					}
736 
737 					RSPAMD_PRINTF_APPEND (v->str, slen);
738 				}
739 				else {
740 					RSPAMD_PRINTF_APPEND ("(NULL)", 6);
741 				}
742 
743 				continue;
744 
745 			case 'T':
746 				tok = va_arg (args, rspamd_ftok_t *);
747 
748 				if (tok) {
749 					slen = tok->len;
750 
751 					if (G_UNLIKELY (width != 0)) {
752 						slen = MIN (tok->len, width);
753 					}
754 					RSPAMD_PRINTF_APPEND (tok->begin, slen);
755 				}
756 				else {
757 					RSPAMD_PRINTF_APPEND ("(NULL)", 6);
758 				}
759 				continue;
760 
761 			case 'v':
762 				gs = va_arg (args, GString *);
763 
764 				if (gs) {
765 					slen = gs->len;
766 
767 					if (G_UNLIKELY (width != 0)) {
768 						slen = MIN (gs->len, width);
769 					}
770 
771 					RSPAMD_PRINTF_APPEND (gs->str, slen);
772 				}
773 				else {
774 					RSPAMD_PRINTF_APPEND ("(NULL)", 6);
775 				}
776 
777 				continue;
778 
779 			case 'e':
780 				err = va_arg (args, GError *);
781 
782 				if (err) {
783 					p = err->message;
784 
785 					if (p == NULL) {
786 						p = "(NULL)";
787 					}
788 				}
789 				else {
790 					p = "unknown error";
791 				}
792 
793 				slen = strlen (p);
794 				RSPAMD_PRINTF_APPEND (p, slen);
795 
796 				continue;
797 
798 			case 's':
799 				p = va_arg (args, gchar *);
800 				if (p == NULL) {
801 					p = "(NULL)";
802 					slen = sizeof ("(NULL)") - 1;
803 				}
804 
805 				if (G_UNLIKELY (b32)) {
806 					gchar *b32buf;
807 
808 					if (G_UNLIKELY (slen == -1)) {
809 						if (G_LIKELY (width != 0)) {
810 							slen = width;
811 						}
812 						else {
813 							/* NULL terminated string */
814 							slen = strlen (p);
815 						}
816 					}
817 
818 					b32buf = rspamd_encode_base32 (p, slen, RSPAMD_BASE32_DEFAULT);
819 
820 					if (b32buf) {
821 						RSPAMD_PRINTF_APPEND (b32buf, strlen (b32buf));
822 						g_free (b32buf);
823 					}
824 					else {
825 						RSPAMD_PRINTF_APPEND ("(NULL)", sizeof ("(NULL)") - 1);
826 					}
827 				}
828 				else if (G_UNLIKELY (hex)) {
829 					gchar hexbuf[2];
830 
831 					if (G_UNLIKELY (slen == -1)) {
832 						if (G_LIKELY (width != 0)) {
833 							slen = width;
834 						}
835 						else {
836 							/* NULL terminated string */
837 							slen = strlen (p);
838 						}
839 					}
840 
841 					while (slen) {
842 						hexbuf[0] = hex == 2 ? _HEX[(*p >> 4u) & 0xfu] :
843 								_hex[(*p >> 4u) & 0xfu];
844 						hexbuf[1] = hex == 2 ? _HEX[*p & 0xfu] : _hex[*p & 0xfu];
845 						RSPAMD_PRINTF_APPEND_BUF (hexbuf, 2);
846 						p++;
847 						slen--;
848 					}
849 
850 					fmt++;
851 					buf_start = fmt;
852 
853 				}
854 				else if (G_UNLIKELY (b64)) {
855 					gchar *b64buf;
856 					gsize olen = 0;
857 
858 					if (G_UNLIKELY (slen == -1)) {
859 						if (G_LIKELY (width != 0)) {
860 							slen = width;
861 						}
862 						else {
863 							/* NULL terminated string */
864 							slen = strlen (p);
865 						}
866 					}
867 
868 					b64buf = rspamd_encode_base64 (p, slen, 0, &olen);
869 
870 					if (b64buf) {
871 						RSPAMD_PRINTF_APPEND (b64buf, olen);
872 						g_free (b64buf);
873 					}
874 					else {
875 						RSPAMD_PRINTF_APPEND ("(NULL)", sizeof ("(NULL)") - 1);
876 					}
877 				}
878 				else {
879 					if (slen == -1) {
880 						/* NULL terminated string */
881 						slen = strlen (p);
882 					}
883 
884 					if (G_UNLIKELY (width != 0)) {
885 						slen = MIN (slen, width);
886 					}
887 
888 					RSPAMD_PRINTF_APPEND (p, slen);
889 				}
890 
891 				continue;
892 
893 			case 'O':
894 				i64 = (gint64) va_arg (args, off_t);
895 				sign = 1;
896 				break;
897 
898 			case 'P':
899 				i64 = (gint64) va_arg (args, pid_t);
900 				sign = 1;
901 				break;
902 
903 			case 't':
904 				i64 = (gint64) va_arg (args, time_t);
905 				sign = 1;
906 				break;
907 
908 			case 'z':
909 				if (sign) {
910 					i64 = (gint64) va_arg (args, ssize_t);
911 				} else {
912 					ui64 = (guint64) va_arg (args, size_t);
913 				}
914 				break;
915 
916 			case 'd':
917 				if (sign) {
918 					i64 = (gint64) va_arg (args, gint);
919 				} else {
920 					ui64 = (guint64) va_arg (args, guint);
921 				}
922 				break;
923 
924 			case 'l':
925 				if (sign) {
926 					i64 = (gint64) va_arg (args, glong);
927 				} else {
928 					ui64 = (guint64) va_arg (args, gulong);
929 				}
930 				break;
931 
932 			case 'D':
933 				if (sign) {
934 					i64 = (gint64) va_arg (args, gint32);
935 				} else {
936 					ui64 = (guint64) va_arg (args, guint32);
937 				}
938 				break;
939 
940 			case 'L':
941 				if (sign) {
942 					i64 = va_arg (args, gint64);
943 				} else {
944 					ui64 = va_arg (args, guint64);
945 				}
946 				break;
947 
948 
949 			case 'f':
950 				f = (gdouble) va_arg (args, double);
951 				slen = fpconv_dtoa (f, dtoabuf, frac_width, false);
952 
953 				RSPAMD_PRINTF_APPEND (dtoabuf, slen);
954 
955 				continue;
956 
957 			case 'g':
958 				f = (gdouble) va_arg (args, double);
959 				slen = fpconv_dtoa (f, dtoabuf, 0, true);
960 				RSPAMD_PRINTF_APPEND (dtoabuf, slen);
961 
962 				continue;
963 
964 			case 'F':
965 				f = (gdouble) va_arg (args, long double);
966 				slen = fpconv_dtoa (f, dtoabuf, frac_width, false);
967 
968 				RSPAMD_PRINTF_APPEND (dtoabuf, slen);
969 
970 				continue;
971 
972 			case 'G':
973 				f = (gdouble) va_arg (args, long double);
974 				slen = fpconv_dtoa (f, dtoabuf, 0, true);
975 				RSPAMD_PRINTF_APPEND (dtoabuf, slen);
976 
977 				continue;
978 
979 			case 'p':
980 				ui64 = (uintptr_t) va_arg (args, void *);
981 				hex = 2;
982 				sign = 0;
983 				zero = '0';
984 				width = sizeof (void *) * 2;
985 				break;
986 
987 			case 'c':
988 				c = va_arg (args, gint);
989 				c &= 0xffu;
990 				if (G_UNLIKELY (hex)) {
991 					gchar hexbuf[2];
992 					hexbuf[0] = hex == 2 ? _HEX[(c >> 4u) & 0xfu] :
993 								_hex[(c >> 4u) & 0xfu];
994 					hexbuf[1] = hex == 2 ? _HEX[c & 0xfu] : _hex[c & 0xfu];
995 
996 					RSPAMD_PRINTF_APPEND (hexbuf, 2);
997 				}
998 				else {
999 					RSPAMD_PRINTF_APPEND (&c, 1);
1000 				}
1001 
1002 				continue;
1003 
1004 			case 'Z':
1005 				c = '\0';
1006 				RSPAMD_PRINTF_APPEND (&c, 1);
1007 
1008 				continue;
1009 
1010 			case 'N':
1011 				c = '\n';
1012 				RSPAMD_PRINTF_APPEND (&c, 1);
1013 
1014 				continue;
1015 
1016 			case '%':
1017 				c = '%';
1018 				RSPAMD_PRINTF_APPEND (&c, 1);
1019 
1020 				continue;
1021 
1022 			default:
1023 				c = *fmt;
1024 				RSPAMD_PRINTF_APPEND (&c, 1);
1025 
1026 				continue;
1027 			}
1028 
1029 			/* Print number */
1030 			p = numbuf;
1031 			last = p + sizeof (numbuf);
1032 			if (sign) {
1033 				if (i64 < 0) {
1034 					*p++ = '-';
1035 					ui64 = (guint64) - i64;
1036 
1037 				} else {
1038 					ui64 = (guint64) i64;
1039 				}
1040 			}
1041 
1042 			if (!humanize) {
1043 				p = rspamd_sprintf_num (p, last, ui64, zero, hex, width);
1044 			}
1045 			else {
1046 				p = rspamd_humanize_number (p, last, ui64, bytes);
1047 			}
1048 			slen = p - numbuf;
1049 			RSPAMD_PRINTF_APPEND (numbuf, slen);
1050 
1051 		} else {
1052 			fmt++;
1053 		}
1054 	}
1055 
1056 	/* Finish buffer */
1057 	if (fmt > buf_start) {
1058 		wr = func (buf_start, fmt - buf_start, apd);
1059 		if (wr <= 0) {
1060 			goto oob;
1061 		}
1062 		written += wr;
1063 	}
1064 
1065 oob:
1066 	return written;
1067 }
1068 
1069