bsd-snprintf.c revision 146998
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.9 2004/09/23 11:35:09 dtucker 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, ch;
125	long value;
126	long double fvalue;
127	int min = 0, max = -1, state = DP_S_DEFAULT, flags = 0, cflags = 0;
128	size_t currlen = 0;
129
130	ch = *format++;
131
132	while (state != DP_S_DONE) {
133		if ((ch == '\0') || (currlen >= maxlen))
134			state = DP_S_DONE;
135
136		switch(state) {
137		case DP_S_DEFAULT:
138			if (ch == '%')
139				state = DP_S_FLAGS;
140			else
141				dopr_outch(buffer, &currlen, maxlen, ch);
142			ch = *format++;
143			break;
144		case DP_S_FLAGS:
145			switch (ch) {
146			case '-':
147				flags |= DP_F_MINUS;
148				ch = *format++;
149				break;
150			case '+':
151				flags |= DP_F_PLUS;
152				ch = *format++;
153				break;
154			case ' ':
155				flags |= DP_F_SPACE;
156				ch = *format++;
157				break;
158			case '#':
159				flags |= DP_F_NUM;
160				ch = *format++;
161				break;
162			case '0':
163				flags |= DP_F_ZERO;
164				ch = *format++;
165				break;
166			default:
167				state = DP_S_MIN;
168				break;
169			}
170			break;
171		case DP_S_MIN:
172			if (isdigit((unsigned char)ch)) {
173				min = 10 * min + char_to_int (ch);
174				ch = *format++;
175			} else if (ch == '*') {
176				min = va_arg (args, int);
177				ch = *format++;
178				state = DP_S_DOT;
179			} else
180				state = DP_S_DOT;
181			break;
182		case DP_S_DOT:
183			if (ch == '.') {
184				state = DP_S_MAX;
185				ch = *format++;
186			} else
187				state = DP_S_MOD;
188			break;
189		case DP_S_MAX:
190			if (isdigit((unsigned char)ch)) {
191				if (max < 0)
192					max = 0;
193				max = 10 * max + char_to_int(ch);
194				ch = *format++;
195			} else if (ch == '*') {
196				max = va_arg (args, int);
197				ch = *format++;
198				state = DP_S_MOD;
199			} else
200				state = DP_S_MOD;
201			break;
202		case DP_S_MOD:
203			switch (ch) {
204			case 'h':
205				cflags = DP_C_SHORT;
206				ch = *format++;
207				break;
208			case 'l':
209				cflags = DP_C_LONG;
210				ch = *format++;
211				if (ch == 'l') {
212					cflags = DP_C_LONG_LONG;
213					ch = *format++;
214				}
215				break;
216			case 'q':
217				cflags = DP_C_LONG_LONG;
218				ch = *format++;
219				break;
220			case 'L':
221				cflags = DP_C_LDOUBLE;
222				ch = *format++;
223				break;
224			default:
225				break;
226			}
227			state = DP_S_CONV;
228			break;
229		case DP_S_CONV:
230			switch (ch) {
231			case 'd':
232			case 'i':
233				if (cflags == DP_C_SHORT)
234					value = va_arg(args, int);
235				else if (cflags == DP_C_LONG)
236					value = va_arg(args, long int);
237				else if (cflags == DP_C_LONG_LONG)
238					value = va_arg (args, long long);
239				else
240					value = va_arg (args, int);
241				fmtint(buffer, &currlen, maxlen, value, 10, min, max, flags);
242				break;
243			case 'o':
244				flags |= DP_F_UNSIGNED;
245				if (cflags == DP_C_SHORT)
246					value = va_arg(args, unsigned int);
247				else if (cflags == DP_C_LONG)
248					value = va_arg(args, unsigned long int);
249				else if (cflags == DP_C_LONG_LONG)
250					value = va_arg(args, unsigned long long);
251				else
252					value = va_arg(args, unsigned int);
253				fmtint(buffer, &currlen, maxlen, value, 8, min, max, flags);
254				break;
255			case 'u':
256				flags |= DP_F_UNSIGNED;
257				if (cflags == DP_C_SHORT)
258					value = va_arg(args, unsigned int);
259				else if (cflags == DP_C_LONG)
260					value = va_arg(args, unsigned long int);
261				else if (cflags == DP_C_LONG_LONG)
262					value = va_arg(args, unsigned long long);
263				else
264					value = va_arg(args, unsigned int);
265				fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags);
266				break;
267			case 'X':
268				flags |= DP_F_UP;
269			case 'x':
270				flags |= DP_F_UNSIGNED;
271				if (cflags == DP_C_SHORT)
272					value = va_arg(args, unsigned int);
273				else if (cflags == DP_C_LONG)
274					value = va_arg(args, unsigned long int);
275				else if (cflags == DP_C_LONG_LONG)
276					value = va_arg(args, unsigned long long);
277				else
278					value = va_arg(args, unsigned int);
279				fmtint(buffer, &currlen, maxlen, value, 16, min, max, flags);
280				break;
281			case 'f':
282				if (cflags == DP_C_LDOUBLE)
283					fvalue = va_arg(args, long double);
284				else
285					fvalue = va_arg(args, double);
286				/* um, floating point? */
287				fmtfp(buffer, &currlen, maxlen, fvalue, min, max, flags);
288				break;
289			case 'E':
290				flags |= DP_F_UP;
291			case 'e':
292				if (cflags == DP_C_LDOUBLE)
293					fvalue = va_arg(args, long double);
294				else
295					fvalue = va_arg(args, double);
296				break;
297			case 'G':
298				flags |= DP_F_UP;
299			case 'g':
300				if (cflags == DP_C_LDOUBLE)
301					fvalue = va_arg(args, long double);
302				else
303					fvalue = va_arg(args, double);
304				break;
305			case 'c':
306				dopr_outch(buffer, &currlen, maxlen, va_arg(args, int));
307				break;
308			case 's':
309				strvalue = va_arg(args, char *);
310				if (max < 0)
311					max = maxlen; /* ie, no max */
312				fmtstr(buffer, &currlen, maxlen, strvalue, flags, min, max);
313				break;
314			case 'p':
315				strvalue = va_arg(args, void *);
316				fmtint(buffer, &currlen, maxlen, (long) strvalue, 16, min, max, flags);
317				break;
318			case 'n':
319				if (cflags == DP_C_SHORT) {
320					short int *num;
321					num = va_arg(args, short int *);
322					*num = currlen;
323				} else if (cflags == DP_C_LONG) {
324					long int *num;
325					num = va_arg(args, long int *);
326					*num = currlen;
327				} else if (cflags == DP_C_LONG_LONG) {
328					long long *num;
329					num = va_arg(args, long long *);
330					*num = currlen;
331				} else {
332					int *num;
333					num = va_arg(args, int *);
334					*num = currlen;
335				}
336				break;
337			case '%':
338				dopr_outch(buffer, &currlen, maxlen, ch);
339				break;
340			case 'w': /* not supported yet, treat as next char */
341				ch = *format++;
342				break;
343			default: /* Unknown, skip */
344			break;
345			}
346			ch = *format++;
347			state = DP_S_DEFAULT;
348			flags = cflags = min = 0;
349			max = -1;
350			break;
351		case DP_S_DONE:
352			break;
353		default: /* hmm? */
354			break; /* some picky compilers need this */
355		}
356	}
357	if (currlen < maxlen - 1)
358		buffer[currlen] = '\0';
359	else
360		buffer[maxlen - 1] = '\0';
361}
362
363static void
364fmtstr(char *buffer, size_t *currlen, size_t maxlen,
365    char *value, int flags, int min, int max)
366{
367	int cnt = 0, padlen, strln;     /* amount to pad */
368
369	if (value == 0)
370		value = "<NULL>";
371
372	for (strln = 0; strln < max && value[strln]; ++strln); /* strlen */
373	padlen = min - strln;
374	if (padlen < 0)
375		padlen = 0;
376	if (flags & DP_F_MINUS)
377		padlen = -padlen; /* Left Justify */
378
379	while ((padlen > 0) && (cnt < max)) {
380		dopr_outch(buffer, currlen, maxlen, ' ');
381		--padlen;
382		++cnt;
383	}
384	while (*value && (cnt < max)) {
385		dopr_outch(buffer, currlen, maxlen, *value++);
386		++cnt;
387	}
388	while ((padlen < 0) && (cnt < max)) {
389		dopr_outch(buffer, currlen, maxlen, ' ');
390		++padlen;
391		++cnt;
392	}
393}
394
395/* Have to handle DP_F_NUM (ie 0x and 0 alternates) */
396
397static void
398fmtint(char *buffer, size_t *currlen, size_t maxlen,
399    long value, int base, int min, int max, int flags)
400{
401	unsigned long uvalue;
402	char convert[20];
403	int signvalue = 0, place = 0, caps = 0;
404	int spadlen = 0; /* amount to space pad */
405	int zpadlen = 0; /* amount to zero pad */
406
407	if (max < 0)
408		max = 0;
409
410	uvalue = value;
411
412	if (!(flags & DP_F_UNSIGNED)) {
413		if (value < 0) {
414			signvalue = '-';
415			uvalue = -value;
416		} else if (flags & DP_F_PLUS)  /* Do a sign (+/i) */
417			signvalue = '+';
418		else if (flags & DP_F_SPACE)
419			signvalue = ' ';
420	}
421
422	if (flags & DP_F_UP)
423		caps = 1; /* Should characters be upper case? */
424	do {
425		convert[place++] =
426		    (caps ? "0123456789ABCDEF" : "0123456789abcdef")
427		    [uvalue % (unsigned)base];
428		uvalue = (uvalue / (unsigned)base );
429	} while (uvalue && (place < 20));
430	if (place == 20)
431		place--;
432	convert[place] = 0;
433
434	zpadlen = max - place;
435	spadlen = min - MAX (max, place) - (signvalue ? 1 : 0);
436	if (zpadlen < 0)
437		zpadlen = 0;
438	if (spadlen < 0)
439		spadlen = 0;
440	if (flags & DP_F_ZERO) {
441		zpadlen = MAX(zpadlen, spadlen);
442		spadlen = 0;
443	}
444	if (flags & DP_F_MINUS)
445		spadlen = -spadlen; /* Left Justifty */
446
447	/* Spaces */
448	while (spadlen > 0) {
449		dopr_outch(buffer, currlen, maxlen, ' ');
450		--spadlen;
451	}
452
453	/* Sign */
454	if (signvalue)
455		dopr_outch(buffer, currlen, maxlen, signvalue);
456
457	/* Zeros */
458	if (zpadlen > 0) {
459		while (zpadlen > 0) {
460			dopr_outch(buffer, currlen, maxlen, '0');
461			--zpadlen;
462		}
463	}
464
465	/* Digits */
466	while (place > 0)
467		dopr_outch(buffer, currlen, maxlen, convert[--place]);
468
469	/* Left Justified spaces */
470	while (spadlen < 0) {
471		dopr_outch (buffer, currlen, maxlen, ' ');
472		++spadlen;
473	}
474}
475
476static long double
477pow10(int exp)
478{
479	long double result = 1;
480
481	while (exp) {
482		result *= 10;
483		exp--;
484	}
485
486	return result;
487}
488
489static long
490round(long double value)
491{
492	long intpart = value;
493
494	value -= intpart;
495	if (value >= 0.5)
496		intpart++;
497
498	return intpart;
499}
500
501static void
502fmtfp(char *buffer, size_t *currlen, size_t maxlen, long double fvalue,
503      int min, int max, int flags)
504{
505	char iconvert[20], fconvert[20];
506	int signvalue = 0, iplace = 0, fplace = 0;
507	int padlen = 0; /* amount to pad */
508	int zpadlen = 0, caps = 0;
509	long intpart, fracpart;
510	long double ufvalue;
511
512	/*
513	 * AIX manpage says the default is 0, but Solaris says the default
514	 * is 6, and sprintf on AIX defaults to 6
515	 */
516	if (max < 0)
517		max = 6;
518
519	ufvalue = abs_val(fvalue);
520
521	if (fvalue < 0)
522		signvalue = '-';
523	else if (flags & DP_F_PLUS)  /* Do a sign (+/i) */
524		signvalue = '+';
525	else if (flags & DP_F_SPACE)
526		signvalue = ' ';
527
528	intpart = ufvalue;
529
530	/*
531	 * Sorry, we only support 9 digits past the decimal because of our
532	 * conversion method
533	 */
534	if (max > 9)
535		max = 9;
536
537	/* We "cheat" by converting the fractional part to integer by
538	 * multiplying by a factor of 10
539	 */
540	fracpart = round((pow10 (max)) * (ufvalue - intpart));
541
542	if (fracpart >= pow10 (max)) {
543		intpart++;
544		fracpart -= pow10 (max);
545	}
546
547	/* Convert integer part */
548	do {
549		iconvert[iplace++] =
550		    (caps ? "0123456789ABCDEF" : "0123456789abcdef")
551		    [intpart % 10];
552		intpart = (intpart / 10);
553	} while(intpart && (iplace < 20));
554	if (iplace == 20)
555		iplace--;
556	iconvert[iplace] = 0;
557
558	/* Convert fractional part */
559	do {
560		fconvert[fplace++] =
561		    (caps ? "0123456789ABCDEF" : "0123456789abcdef")
562		    [fracpart % 10];
563		fracpart = (fracpart / 10);
564	} while(fracpart && (fplace < 20));
565	if (fplace == 20)
566		fplace--;
567	fconvert[fplace] = 0;
568
569	/* -1 for decimal point, another -1 if we are printing a sign */
570	padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0);
571	zpadlen = max - fplace;
572	if (zpadlen < 0)
573		zpadlen = 0;
574	if (padlen < 0)
575		padlen = 0;
576	if (flags & DP_F_MINUS)
577		padlen = -padlen; /* Left Justifty */
578
579	if ((flags & DP_F_ZERO) && (padlen > 0)) {
580		if (signvalue) {
581			dopr_outch(buffer, currlen, maxlen, signvalue);
582			--padlen;
583			signvalue = 0;
584		}
585		while (padlen > 0) {
586			dopr_outch(buffer, currlen, maxlen, '0');
587			--padlen;
588		}
589	}
590	while (padlen > 0) {
591		dopr_outch(buffer, currlen, maxlen, ' ');
592		--padlen;
593	}
594	if (signvalue)
595		dopr_outch(buffer, currlen, maxlen, signvalue);
596
597	while (iplace > 0)
598		dopr_outch(buffer, currlen, maxlen, iconvert[--iplace]);
599
600	/*
601	 * Decimal point.  This should probably use locale to find the
602	 * correct char to print out.
603	 */
604	dopr_outch(buffer, currlen, maxlen, '.');
605
606	while (fplace > 0)
607		dopr_outch(buffer, currlen, maxlen, fconvert[--fplace]);
608
609	while (zpadlen > 0) {
610		dopr_outch(buffer, currlen, maxlen, '0');
611		--zpadlen;
612	}
613
614	while (padlen < 0) {
615		dopr_outch(buffer, currlen, maxlen, ' ');
616		++padlen;
617	}
618}
619
620static void
621dopr_outch(char *buffer, size_t *currlen, size_t maxlen, char c)
622{
623	if (*currlen < maxlen)
624		buffer[(*currlen)++] = c;
625}
626#endif /* !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF) */
627
628#ifndef HAVE_VSNPRINTF
629int
630vsnprintf(char *str, size_t count, const char *fmt, va_list args)
631{
632	str[0] = 0;
633	dopr(str, count, fmt, args);
634
635	return(strlen(str));
636}
637#endif /* !HAVE_VSNPRINTF */
638
639#ifndef HAVE_SNPRINTF
640int
641snprintf(char *str,size_t count,const char *fmt,...)
642{
643	va_list ap;
644
645	va_start(ap, fmt);
646	(void) vsnprintf(str, count, fmt, ap);
647	va_end(ap);
648
649	return(strlen(str));
650}
651
652#endif /* !HAVE_SNPRINTF */
653