1/*	$NetBSD: snprintf.c,v 1.6 2007/07/22 05:19:02 lukem Exp $	*/
2
3/*
4 * Copyright Patrick Powell 1995
5 * This code is based on code written by Patrick Powell (papowell@astart.com)
6 * It may be used for any purpose as long as this notice remains intact
7 * on all source code distributions
8 */
9
10/**************************************************************
11 * Original:
12 * Patrick Powell Tue Apr 11 09:48:21 PDT 1995
13 * A bombproof version of doprnt (dopr) included.
14 * Sigh.  This sort of thing is always nasty do deal with.  Note that
15 * the version here does not include floating point...
16 *
17 * snprintf() is used instead of sprintf() as it does limit checks
18 * for string length.  This covers a nasty loophole.
19 *
20 * The other functions are there to prevent NULL pointers from
21 * causing nast effects.
22 *
23 * More Recently:
24 *  Brandon Long <blong@fiction.net> 9/15/96 for mutt 0.43
25 *  This was ugly.  It is still ugly.  I opted out of floating point
26 *  numbers, but the formatter understands just about everything
27 *  from the normal C string format, at least as far as I can tell from
28 *  the Solaris 2.5 printf(3S) man page.
29 *
30 *  Brandon Long <blong@fiction.net> 10/22/97 for mutt 0.87.1
31 *    Ok, added some minimal floating point support, which means this
32 *    probably requires libm on most operating systems.  Don't yet
33 *    support the exponent (e,E) and sigfig (g,G).  Also, fmtint()
34 *    was pretty badly broken, it just wasn't being exercised in ways
35 *    which showed it, so that's been fixed.  Also, formated the code
36 *    to mutt conventions, and removed dead code left over from the
37 *    original.  Also, there is now a builtin-test, just compile with:
38 *           gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm
39 *    and run snprintf for results.
40 *
41 *  Thomas Roessler <roessler@guug.de> 01/27/98 for mutt 0.89i
42 *    The PGP code was using unsigned hexadecimal formats.
43 *    Unfortunately, unsigned formats simply didn't work.
44 *
45 *  Michael Elkins <me@cs.hmc.edu> 03/05/98 for mutt 0.90.8
46 *    The original code assumed that both snprintf() and vsnprintf() were
47 *    missing.  Some systems only have snprintf() but not vsnprintf(), so
48 *    the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF.
49 *
50 *  Andrew Tridgell (tridge@samba.org) Oct 1998
51 *    fixed handling of %.0f
52 *    added test for HAVE_LONG_DOUBLE
53 *
54 *  Luke Mewburn <lukem@NetBSD.org>, Thu Sep 30 23:28:21 EST 1999
55 *	cleaned up formatting, autoconf tests
56 *	added long long support
57 *
58 **************************************************************/
59
60#include "tnftp.h"
61
62
63#if defined(HAVE_LONG_DOUBLE)
64#define LDOUBLE long double
65#else
66#define LDOUBLE double
67#endif
68
69#if defined(HAVE_LONG_LONG_INT)
70#define LLONG long long
71#else
72#define LLONG long
73#endif
74
75static void dopr(char *buffer, size_t maxlen, size_t *retlen,
76		    const char *format, va_list args);
77static void fmtstr(char *buffer, size_t * currlen, size_t maxlen,
78		    char *value, int min, int max, int flags);
79static void fmtint(char *buffer, size_t * currlen, size_t maxlen,
80		    LLONG value, int base, int min, int max, int flags);
81static void fmtfp(char *buffer, size_t * currlen, size_t maxlen,
82		    LDOUBLE fvalue, int min, int max, int flags);
83static void dopr_outch(char *buffer, size_t * currlen, size_t maxlen, int c);
84
85/*
86 * dopr(): poor man's version of doprintf
87 */
88
89/* format read states */
90#define DP_S_DEFAULT	0
91#define DP_S_FLAGS	1
92#define DP_S_MIN	2
93#define DP_S_DOT	3
94#define DP_S_MAX	4
95#define DP_S_MOD	5
96#define DP_S_CONV	6
97#define DP_S_DONE	7
98
99/* format flags - Bits */
100#define DP_F_MINUS	(1 << 0)
101#define DP_F_PLUS	(1 << 1)
102#define DP_F_SPACE	(1 << 2)
103#define DP_F_NUM	(1 << 3)
104#define DP_F_ZERO	(1 << 4)
105#define DP_F_UP		(1 << 5)
106#define DP_F_UNSIGNED	(1 << 6)
107
108/* Conversion Flags */
109#define DP_C_SHORT	1
110#define DP_C_LONG	2
111#define DP_C_LDOUBLE	3
112#define DP_C_LLONG	4
113
114#define char_to_int(p) (p - '0')
115
116static void
117dopr(char *buffer, size_t maxlen, size_t *retlen, const char *format,
118	va_list args)
119{
120	char	 ch;
121	LLONG	 value;
122	LDOUBLE	 fvalue;
123	char	*strvalue;
124	int	 min;
125	int	 max;
126	int	 state;
127	int	 flags;
128	int	 cflags;
129	size_t	 currlen;
130
131	state = DP_S_DEFAULT;
132	flags = currlen = cflags = min = 0;
133	max = -1;
134	ch = *format++;
135
136	while (state != DP_S_DONE) {
137		if ((ch == '\0') || (currlen >= maxlen))
138			state = DP_S_DONE;
139
140		switch (state) {
141		case DP_S_DEFAULT:
142			if (ch == '%')
143				state = DP_S_FLAGS;
144			else
145				dopr_outch(buffer, &currlen, maxlen, ch);
146			ch = *format++;
147			break;
148		case DP_S_FLAGS:
149			switch (ch) {
150			case '-':
151				flags |= DP_F_MINUS;
152				ch = *format++;
153				break;
154			case '+':
155				flags |= DP_F_PLUS;
156				ch = *format++;
157				break;
158			case ' ':
159				flags |= DP_F_SPACE;
160				ch = *format++;
161				break;
162			case '#':
163				flags |= DP_F_NUM;
164				ch = *format++;
165				break;
166			case '0':
167				flags |= DP_F_ZERO;
168				ch = *format++;
169				break;
170			default:
171				state = DP_S_MIN;
172				break;
173			}
174			break;
175		case DP_S_MIN:
176			if (isdigit((unsigned char) ch)) {
177				min = 10 * min + char_to_int(ch);
178				ch = *format++;
179			} else if (ch == '*') {
180				min = va_arg(args, int);
181				ch = *format++;
182				state = DP_S_DOT;
183			} else
184				state = DP_S_DOT;
185			break;
186		case DP_S_DOT:
187			if (ch == '.') {
188				state = DP_S_MAX;
189				ch = *format++;
190			} else
191				state = DP_S_MOD;
192			break;
193		case DP_S_MAX:
194			if (isdigit((unsigned char) ch)) {
195				if (max < 0)
196					max = 0;
197				max = 10 * max + char_to_int(ch);
198				ch = *format++;
199			} else if (ch == '*') {
200				max = va_arg(args, int);
201				ch = *format++;
202				state = DP_S_MOD;
203			} else
204				state = DP_S_MOD;
205			break;
206		case DP_S_MOD:
207			switch (ch) {
208			case 'h':
209				cflags = DP_C_SHORT;
210				ch = *format++;
211				break;
212			case 'l':
213				if (*format == 'l') {
214					cflags = DP_C_LLONG;
215					format++;
216				} else
217					cflags = DP_C_LONG;
218				ch = *format++;
219				break;
220			case 'q':
221				cflags = DP_C_LLONG;
222				ch = *format++;
223				break;
224			case 'L':
225				cflags = DP_C_LDOUBLE;
226				ch = *format++;
227				break;
228			default:
229				break;
230			}
231			state = DP_S_CONV;
232			break;
233		case DP_S_CONV:
234			switch (ch) {
235			case 'd':
236			case 'i':
237				switch (cflags) {
238				case DP_C_SHORT:
239					value = va_arg(args, int);
240					break;
241				case DP_C_LONG:
242					value = va_arg(args, long int);
243					break;
244				case DP_C_LLONG:
245					value = va_arg(args, LLONG);
246					break;
247				default:
248					value = va_arg(args, int);
249					break;
250				}
251				fmtint(buffer, &currlen, maxlen, value, 10,
252				    min, max, flags);
253				break;
254			case 'X':
255				flags |= DP_F_UP;
256				/* FALLTHROUGH */
257			case 'x':
258			case 'o':
259			case 'u':
260				flags |= DP_F_UNSIGNED;
261				switch (cflags) {
262				case DP_C_SHORT:
263					value = va_arg(args, unsigned int);
264					break;
265				case DP_C_LONG:
266					value = (LLONG) va_arg(args,
267					    unsigned long int);
268					break;
269				case DP_C_LLONG:
270					value = va_arg(args, unsigned LLONG);
271					break;
272				default:
273					value = (LLONG) va_arg(args,
274					    unsigned int);
275					break;
276				}
277				fmtint(buffer, &currlen, maxlen, value,
278				    ch == 'o' ? 8 : (ch == 'u' ? 10 : 16),
279				    min, max, flags);
280				break;
281			case 'f':
282				if (cflags == DP_C_LDOUBLE)
283					fvalue = va_arg(args, LDOUBLE);
284				else
285					fvalue = va_arg(args, double);
286				/* um, floating point? */
287				fmtfp(buffer, &currlen, maxlen, fvalue, min,
288				    max, flags);
289				break;
290			case 'E':
291				flags |= DP_F_UP;
292			case 'e':
293				if (cflags == DP_C_LDOUBLE)
294					fvalue = va_arg(args, LDOUBLE);
295				else
296					fvalue = va_arg(args, double);
297				break;
298			case 'G':
299				flags |= DP_F_UP;
300			case 'g':
301				if (cflags == DP_C_LDOUBLE)
302					fvalue = va_arg(args, LDOUBLE);
303				else
304					fvalue = va_arg(args, double);
305				break;
306			case 'c':
307				dopr_outch(buffer, &currlen, maxlen,
308				    va_arg(args, int));
309				break;
310			case 's':
311				strvalue = va_arg(args, char *);
312				if (max < 0)
313					max = maxlen;	/* ie, no max */
314				fmtstr(buffer, &currlen, maxlen, strvalue,
315				    min, max, flags);
316				break;
317			case 'p':
318				value = (long)va_arg(args, void *);
319				fmtint(buffer, &currlen, maxlen,
320				    value, 16, min, max, flags);
321				break;
322			case 'n':
323/* XXX */
324				if (cflags == DP_C_SHORT) {
325					short int *num;
326					num = va_arg(args, short int *);
327					*num = currlen;
328				} else if (cflags == DP_C_LONG) { /* XXX */
329					long int *num;
330					num = va_arg(args, long int *);
331					*num = (long int) currlen;
332				} else if (cflags == DP_C_LLONG) { /* XXX */
333					LLONG *num;
334					num = va_arg(args, LLONG *);
335					*num = (LLONG) currlen;
336				} else {
337					int    *num;
338					num = va_arg(args, int *);
339					*num = currlen;
340				}
341				break;
342			case '%':
343				dopr_outch(buffer, &currlen, maxlen, ch);
344				break;
345			case 'w':
346				/* not supported yet, treat as next char */
347				ch = *format++;
348				break;
349			default:
350				/* Unknown, skip */
351				break;
352			}
353			ch = *format++;
354			state = DP_S_DEFAULT;
355			flags = cflags = min = 0;
356			max = -1;
357			break;
358		case DP_S_DONE:
359			break;
360		default:
361			/* hmm? */
362			break;	/* some picky compilers need this */
363		}
364	}
365	if (currlen >= maxlen - 1)
366		currlen = maxlen - 1;
367	buffer[currlen] = '\0';
368	*retlen = currlen;
369}
370
371static void
372fmtstr(char *buffer, size_t *currlen, size_t maxlen, char *value,
373	int min, int max, int flags)
374{
375	int	padlen, strln;	/* amount to pad */
376	int	cnt = 0;
377
378	if (value == 0) {
379		value = "<NULL>";
380	}
381	for (strln = 0; value[strln]; ++strln)
382		;	/* strlen */
383	padlen = min - strln;
384	if (padlen < 0)
385		padlen = 0;
386	if (flags & DP_F_MINUS)
387		padlen = -padlen;	/* Left Justify */
388
389	while ((padlen > 0) && (cnt < max)) {
390		dopr_outch(buffer, currlen, maxlen, ' ');
391		--padlen;
392		++cnt;
393	}
394	while (*value && (cnt < max)) {
395		dopr_outch(buffer, currlen, maxlen, *value++);
396		++cnt;
397	}
398	while ((padlen < 0) && (cnt < max)) {
399		dopr_outch(buffer, currlen, maxlen, ' ');
400		++padlen;
401		++cnt;
402	}
403}
404/* Have to handle DP_F_NUM (ie 0x and 0 alternates) */
405
406static void
407fmtint(char *buffer, size_t *currlen, size_t maxlen, LLONG value, int base,
408	int min, int max, int flags)
409{
410	int		signvalue = 0;
411	unsigned LLONG	uvalue;
412	char		convert[20];
413	int		place = 0;
414	int		spadlen = 0;	/* amount to space pad */
415	int		zpadlen = 0;	/* amount to zero pad */
416	int		caps = 0;
417
418	if (max < 0)
419		max = 0;
420
421	uvalue = value;
422
423	if (!(flags & DP_F_UNSIGNED)) {
424		if (value < 0) {
425			signvalue = '-';
426			uvalue = -value;
427		} else if (flags & DP_F_PLUS)	/* Do a sign (+/i) */
428			signvalue = '+';
429		else if (flags & DP_F_SPACE)
430			signvalue = ' ';
431	}
432	if (flags & DP_F_UP)
433		caps = 1;	/* Should characters be upper case? */
434
435	do {
436		convert[place++] =
437		    (caps ? "0123456789ABCDEF" : "0123456789abcdef")
438		    [uvalue % (unsigned) base];
439		uvalue = (uvalue / (unsigned) base);
440	} while (uvalue && (place < 20));
441	if (place == 20)
442		place--;
443	convert[place] = 0;
444
445	zpadlen = max - place;
446	spadlen = min - MAX(max, place) - (signvalue ? 1 : 0);
447	if (zpadlen < 0)
448		zpadlen = 0;
449	if (spadlen < 0)
450		spadlen = 0;
451	if (flags & DP_F_ZERO) {
452		zpadlen = MAX(zpadlen, spadlen);
453		spadlen = 0;
454	}
455	if (flags & DP_F_MINUS)
456		spadlen = -spadlen;	/* Left Justifty */
457
458#ifdef DEBUG_SNPRINTF
459	printf("zpad: %d, spad: %d, min: %d, max: %d, place: %d\n",
460	    zpadlen, spadlen, min, max, place);
461#endif
462
463	/* Spaces */
464	while (spadlen > 0) {
465		dopr_outch(buffer, currlen, maxlen, ' ');
466		--spadlen;
467	}
468
469	/* Sign */
470	if (signvalue)
471		dopr_outch(buffer, currlen, maxlen, signvalue);
472
473	/* Zeros */
474	if (zpadlen > 0) {
475		while (zpadlen > 0) {
476			dopr_outch(buffer, currlen, maxlen, '0');
477			--zpadlen;
478		}
479	}
480	/* Digits */
481	while (place > 0)
482		dopr_outch(buffer, currlen, maxlen, convert[--place]);
483
484	/* Left Justified spaces */
485	while (spadlen < 0) {
486		dopr_outch(buffer, currlen, maxlen, ' ');
487		++spadlen;
488	}
489}
490
491static LDOUBLE
492abs_val(LDOUBLE value)
493{
494	LDOUBLE	result = value;
495
496	if (value < 0)
497		result = -value;
498
499	return result;
500}
501
502static LDOUBLE
503pow10(int exp)
504{
505	LDOUBLE	result = 1;
506
507	while (exp) {
508		result *= 10;
509		exp--;
510	}
511
512	return result;
513}
514
515static long
516round(LDOUBLE value)
517{
518	long	intpart;
519
520	intpart = (long) value;
521	value = value - intpart;
522	if (value >= 0.5)
523		intpart++;
524
525	return intpart;
526}
527
528static void
529fmtfp(char *buffer, size_t *currlen, size_t maxlen, LDOUBLE fvalue,
530	int min, int max, int flags)
531{
532	int	signvalue = 0;
533	LDOUBLE	ufvalue;
534	char	iconvert[20];
535	char	fconvert[20];
536	int	iplace = 0;
537	int	fplace = 0;
538	int	padlen = 0;	/* amount to pad */
539	int	zpadlen = 0;
540	int	caps = 0;
541	long	intpart;
542	long	fracpart;
543
544	/* AIX manpage says the default is 0, but Solaris says the default is
545	 * 6, and sprintf on AIX defaults to 6 */
546	if (max < 0)
547		max = 6;
548
549	ufvalue = abs_val(fvalue);
550
551	if (fvalue < 0)
552		signvalue = '-';
553	else if (flags & DP_F_PLUS)	/* Do a sign (+/i) */
554		signvalue = '+';
555	else if (flags & DP_F_SPACE)
556		signvalue = ' ';
557
558#if 0
559	if (flags & DP_F_UP)
560		caps = 1;	/* Should characters be upper case? */
561#endif
562
563	intpart = (long) ufvalue;
564
565	/* Sorry, we only support 9 digits past the decimal because of our
566	 * conversion method */
567	if (max > 9)
568		max = 9;
569
570	/* We "cheat" by converting the fractional part to integer by
571	 * multiplying by a factor of 10 */
572	fracpart = round((pow10(max)) * (ufvalue - intpart));
573
574	if (fracpart >= pow10(max)) {
575		intpart++;
576		fracpart -= pow10(max);
577	}
578#ifdef DEBUG_SNPRINTF
579	printf("fmtfp: %g %d.%d min=%d max=%d\n",
580	    (double) fvalue, intpart, fracpart, min, max);
581#endif
582
583	/* Convert integer part */
584	do {
585		iconvert[iplace++] =
586		    (caps ? "0123456789ABCDEF"
587			  : "0123456789abcdef")[intpart % 10];
588		intpart = (intpart / 10);
589	} while (intpart && (iplace < 20));
590	if (iplace == 20)
591		iplace--;
592	iconvert[iplace] = 0;
593
594	/* Convert fractional part */
595	do {
596		fconvert[fplace++] =
597		    (caps ? "0123456789ABCDEF"
598			  : "0123456789abcdef")[fracpart % 10];
599		fracpart = (fracpart / 10);
600	} while (fracpart && (fplace < 20));
601	if (fplace == 20)
602		fplace--;
603	fconvert[fplace] = 0;
604
605	/* -1 for decimal point, another -1 if we are printing a sign */
606	padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0);
607	zpadlen = max - fplace;
608	if (zpadlen < 0)
609		zpadlen = 0;
610	if (padlen < 0)
611		padlen = 0;
612	if (flags & DP_F_MINUS)
613		padlen = -padlen;	/* Left Justifty */
614
615	if ((flags & DP_F_ZERO) && (padlen > 0)) {
616		if (signvalue) {
617			dopr_outch(buffer, currlen, maxlen, signvalue);
618			--padlen;
619			signvalue = 0;
620		}
621		while (padlen > 0) {
622			dopr_outch(buffer, currlen, maxlen, '0');
623			--padlen;
624		}
625	}
626	while (padlen > 0) {
627		dopr_outch(buffer, currlen, maxlen, ' ');
628		--padlen;
629	}
630	if (signvalue)
631		dopr_outch(buffer, currlen, maxlen, signvalue);
632
633	while (iplace > 0)
634		dopr_outch(buffer, currlen, maxlen, iconvert[--iplace]);
635
636
637#ifdef DEBUG_SNPRINTF
638	printf("fmtfp: fplace=%d zpadlen=%d\n", fplace, zpadlen);
639#endif
640
641	/*
642         * Decimal point.  This should probably use locale to find the correct
643         * char to print out.
644         */
645	if (max > 0) {
646		dopr_outch(buffer, currlen, maxlen, '.');
647
648		while (fplace > 0)
649			dopr_outch(buffer, currlen, maxlen, fconvert[--fplace]);
650	}
651	while (zpadlen > 0) {
652		dopr_outch(buffer, currlen, maxlen, '0');
653		--zpadlen;
654	}
655
656	while (padlen < 0) {
657		dopr_outch(buffer, currlen, maxlen, ' ');
658		++padlen;
659	}
660}
661
662static void
663dopr_outch(char *buffer, size_t *currlen, size_t maxlen, int c)
664{
665	if (*currlen < maxlen)
666		buffer[(*currlen)++] = (char)c;
667}
668
669int
670vsnprintf(char *str, size_t count, const char *fmt, va_list args)
671{
672	size_t retlen;
673
674	str[0] = 0;
675	dopr(str, count, &retlen, fmt, args);
676	return (retlen);
677}
678
679/* VARARGS3 */
680int
681snprintf(char *str, size_t count, const char *fmt, ...)
682{
683	va_list	 ap;
684	int	 rv;
685
686	va_start(ap, fmt);
687	rv = vsnprintf(str, count, fmt, ap);
688	va_end(ap);
689	return (rv);
690}
691
692
693#ifdef TEST_SNPRINTF
694#ifndef LONG_STRING
695#define LONG_STRING 1024
696#endif
697
698int
699main(int argc, char *argv[])
700{
701	char	 buf1[LONG_STRING];
702	char	 buf2[LONG_STRING];
703	char	*fp_fmt[] = {
704			"%-1.5f",
705			"%1.5f",
706			"%123.9f",
707			"%10.5f",
708			"% 10.5f",
709			"%+22.9f",
710			"%+4.9f",
711			"%01.3f",
712			"%4f",
713			"%3.1f",
714			"%3.2f",
715			"%.0f",
716			"%.1f",
717			NULL
718		 };
719	double	 fp_nums[] = {
720			-1.5, 134.21, 91340.2, 341.1234, 0203.9,
721			0.96, 0.996, 0.9996, 1.996, 4.136,
722			0
723		 };
724	char	*int_fmt[] = {
725			"%-1.5d",
726			"%1.5d",
727			"%123.9d",
728			"%5.5d",
729			"%10.5d",
730			"% 10.5d",
731			"%+22.33d",
732			"%01.3d",
733			"%4d",
734#if defined(HAVE_LONG_LONG_INT)
735			"%12lld",
736#endif
737			NULL
738		 };
739	LLONG	 int_nums[] = {
740			-1, 134, 91340, 341, 0203,
741			4294967290,
742			4294967297,
743			0
744		 };
745	int	 x, y;
746	int	 fail = 0;
747	int	 num = 0;
748	int	 len;
749
750	printf("Testing snprintf format codes against system sprintf...\n");
751
752	for (x = 0; fp_fmt[x] != NULL; x++) {
753		printf("testing %s\n", fp_fmt[x]);
754		for (y = 0; fp_nums[y] != 0; y++) {
755			snprintf(buf1, sizeof(buf1), fp_fmt[x], fp_nums[y]);
756			sprintf(buf2, fp_fmt[x], fp_nums[y]);
757			if (strcmp(buf1, buf2)) {
758				printf("snprintf doesn't match Format: %s\n",
759				    fp_fmt[x]);
760				printf("\tsnprintf = %s\n\tsprintf  = %s\n",
761				    buf1, buf2);
762				fail++;
763			}
764			num++;
765		}
766	}
767
768	for (x = 0; int_fmt[x] != NULL; x++) {
769		printf("testing %s\n", int_fmt[x]);
770		for (y = 0; int_nums[y] != 0; y++) {
771			len = snprintf(buf1, sizeof(buf1), int_fmt[x], int_nums[y]);
772printf("got %d >%s< (%d)\n", len, buf1, (int)strlen(buf1));
773			sprintf(buf2, int_fmt[x], int_nums[y]);
774			if (strcmp(buf1, buf2)) {
775				printf("snprintf doesn't match Format: %s\n",
776				    int_fmt[x]);
777				printf("\tsnprintf = %s\n\tsprintf  = %s\n",
778				    buf1, buf2);
779				fail++;
780			}
781			num++;
782		}
783	}
784
785	printf("%d tests failed out of %d.\n", fail, num);
786	exit(0);
787}
788#endif /* TEST_SNPRINTF */
789