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