1113908Sdes/*
2113908Sdes * Copyright Patrick Powell 1995
3113908Sdes * This code is based on code written by Patrick Powell (papowell@astart.com)
4113908Sdes * It may be used for any purpose as long as this notice remains intact
5113908Sdes * on all source code distributions
6113908Sdes */
7113908Sdes
898937Sdes/**************************************************************
998937Sdes * Original:
1098937Sdes * Patrick Powell Tue Apr 11 09:48:21 PDT 1995
1198937Sdes * A bombproof version of doprnt (dopr) included.
1298937Sdes * Sigh.  This sort of thing is always nasty do deal with.  Note that
1398937Sdes * the version here does not include floating point...
1498937Sdes *
1598937Sdes * snprintf() is used instead of sprintf() as it does limit checks
1698937Sdes * for string length.  This covers a nasty loophole.
1798937Sdes *
1898937Sdes * The other functions are there to prevent NULL pointers from
1998937Sdes * causing nast effects.
2098937Sdes *
2198937Sdes * More Recently:
2298937Sdes *  Brandon Long <blong@fiction.net> 9/15/96 for mutt 0.43
2398937Sdes *  This was ugly.  It is still ugly.  I opted out of floating point
2498937Sdes *  numbers, but the formatter understands just about everything
2598937Sdes *  from the normal C string format, at least as far as I can tell from
2698937Sdes *  the Solaris 2.5 printf(3S) man page.
2798937Sdes *
2898937Sdes *  Brandon Long <blong@fiction.net> 10/22/97 for mutt 0.87.1
2998937Sdes *    Ok, added some minimal floating point support, which means this
3098937Sdes *    probably requires libm on most operating systems.  Don't yet
3198937Sdes *    support the exponent (e,E) and sigfig (g,G).  Also, fmtint()
3298937Sdes *    was pretty badly broken, it just wasn't being exercised in ways
3398937Sdes *    which showed it, so that's been fixed.  Also, formated the code
3498937Sdes *    to mutt conventions, and removed dead code left over from the
3598937Sdes *    original.  Also, there is now a builtin-test, just compile with:
3698937Sdes *           gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm
3798937Sdes *    and run snprintf for results.
3898937Sdes *
3998937Sdes *  Thomas Roessler <roessler@guug.de> 01/27/98 for mutt 0.89i
4098937Sdes *    The PGP code was using unsigned hexadecimal formats.
4198937Sdes *    Unfortunately, unsigned formats simply didn't work.
4298937Sdes *
4398937Sdes *  Michael Elkins <me@cs.hmc.edu> 03/05/98 for mutt 0.90.8
4498937Sdes *    The original code assumed that both snprintf() and vsnprintf() were
4598937Sdes *    missing.  Some systems only have snprintf() but not vsnprintf(), so
4698937Sdes *    the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF.
4798937Sdes *
48157016Sdes *  Andrew Tridgell (tridge@samba.org) Oct 1998
49157016Sdes *    fixed handling of %.0f
50157016Sdes *    added test for HAVE_LONG_DOUBLE
5198937Sdes *
52157016Sdes * tridge@samba.org, idra@samba.org, April 2001
53157016Sdes *    got rid of fcvt code (twas buggy and made testing harder)
54157016Sdes *    added C99 semantics
55157016Sdes *
56157016Sdes * date: 2002/12/19 19:56:31;  author: herb;  state: Exp;  lines: +2 -0
57157016Sdes * actually print args for %g and %e
58157016Sdes *
59157016Sdes * date: 2002/06/03 13:37:52;  author: jmcd;  state: Exp;  lines: +8 -0
60157016Sdes * Since includes.h isn't included here, VA_COPY has to be defined here.  I don't
61157016Sdes * see any include file that is guaranteed to be here, so I'm defining it
62157016Sdes * locally.  Fixes AIX and Solaris builds.
63157016Sdes *
64157016Sdes * date: 2002/06/03 03:07:24;  author: tridge;  state: Exp;  lines: +5 -13
65157016Sdes * put the ifdef for HAVE_VA_COPY in one place rather than in lots of
66157016Sdes * functions
67157016Sdes *
68157016Sdes * date: 2002/05/17 14:51:22;  author: jmcd;  state: Exp;  lines: +21 -4
69157016Sdes * Fix usage of va_list passed as an arg.  Use __va_copy before using it
70157016Sdes * when it exists.
71157016Sdes *
72157016Sdes * date: 2002/04/16 22:38:04;  author: idra;  state: Exp;  lines: +20 -14
73157016Sdes * Fix incorrect zpadlen handling in fmtfp.
74157016Sdes * Thanks to Ollie Oldham <ollie.oldham@metro-optix.com> for spotting it.
75157016Sdes * few mods to make it easier to compile the tests.
76157016Sdes * addedd the "Ollie" test to the floating point ones.
77157016Sdes *
78157016Sdes * Martin Pool (mbp@samba.org) April 2003
79157016Sdes *    Remove NO_CONFIG_H so that the test case can be built within a source
80157016Sdes *    tree with less trouble.
81157016Sdes *    Remove unnecessary SAFE_FREE() definition.
82157016Sdes *
83157016Sdes * Martin Pool (mbp@samba.org) May 2003
84157016Sdes *    Put in a prototype for dummy_snprintf() to quiet compiler warnings.
85157016Sdes *
86157016Sdes *    Move #endif to make sure VA_COPY, LDOUBLE, etc are defined even
87157016Sdes *    if the C library has some snprintf functions already.
88181111Sdes *
89181111Sdes * Damien Miller (djm@mindrot.org) Jan 2007
90181111Sdes *    Fix integer overflows in return value.
91181111Sdes *    Make formatting quite a bit faster by inlining dopr_outch()
92181111Sdes *
9398937Sdes **************************************************************/
9498937Sdes
9598937Sdes#include "includes.h"
9698937Sdes
9798937Sdes#if defined(BROKEN_SNPRINTF)		/* For those with broken snprintf() */
9898937Sdes# undef HAVE_SNPRINTF
9998937Sdes# undef HAVE_VSNPRINTF
10098937Sdes#endif
10198937Sdes
102157016Sdes#ifndef VA_COPY
103157016Sdes# ifdef HAVE_VA_COPY
104157016Sdes#  define VA_COPY(dest, src) va_copy(dest, src)
105157016Sdes# else
106157016Sdes#  ifdef HAVE___VA_COPY
107157016Sdes#   define VA_COPY(dest, src) __va_copy(dest, src)
108157016Sdes#  else
109157016Sdes#   define VA_COPY(dest, src) (dest) = (src)
110157016Sdes#  endif
111157016Sdes# endif
112157016Sdes#endif
113157016Sdes
11498937Sdes#if !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF)
11598937Sdes
116162852Sdes#include <ctype.h>
117162852Sdes#include <stdarg.h>
118162852Sdes#include <stdlib.h>
119162852Sdes#include <string.h>
120181111Sdes#include <limits.h>
121181111Sdes#include <errno.h>
122162852Sdes
123157016Sdes#ifdef HAVE_LONG_DOUBLE
124157016Sdes# define LDOUBLE long double
125157016Sdes#else
126157016Sdes# define LDOUBLE double
127157016Sdes#endif
12898937Sdes
129157016Sdes#ifdef HAVE_LONG_LONG
130157016Sdes# define LLONG long long
131157016Sdes#else
132157016Sdes# define LLONG long
133157016Sdes#endif
13498937Sdes
13598937Sdes/*
13698937Sdes * dopr(): poor man's version of doprintf
13798937Sdes */
13898937Sdes
13998937Sdes/* format read states */
14098937Sdes#define DP_S_DEFAULT 0
14198937Sdes#define DP_S_FLAGS   1
14298937Sdes#define DP_S_MIN     2
14398937Sdes#define DP_S_DOT     3
14498937Sdes#define DP_S_MAX     4
14598937Sdes#define DP_S_MOD     5
14698937Sdes#define DP_S_CONV    6
14798937Sdes#define DP_S_DONE    7
14898937Sdes
14998937Sdes/* format flags - Bits */
15098937Sdes#define DP_F_MINUS 	(1 << 0)
15198937Sdes#define DP_F_PLUS  	(1 << 1)
15298937Sdes#define DP_F_SPACE 	(1 << 2)
15398937Sdes#define DP_F_NUM   	(1 << 3)
15498937Sdes#define DP_F_ZERO  	(1 << 4)
15598937Sdes#define DP_F_UP    	(1 << 5)
15698937Sdes#define DP_F_UNSIGNED 	(1 << 6)
15798937Sdes
15898937Sdes/* Conversion Flags */
159157016Sdes#define DP_C_SHORT   1
160157016Sdes#define DP_C_LONG    2
161157016Sdes#define DP_C_LDOUBLE 3
162157016Sdes#define DP_C_LLONG   4
163262566Sdes#define DP_C_SIZE    5
164262566Sdes#define DP_C_INTMAX  6
16598937Sdes
166157016Sdes#define char_to_int(p) ((p)- '0')
167157016Sdes#ifndef MAX
168157016Sdes# define MAX(p,q) (((p) >= (q)) ? (p) : (q))
169157016Sdes#endif
17098937Sdes
171181111Sdes#define DOPR_OUTCH(buf, pos, buflen, thechar) \
172181111Sdes	do { \
173181111Sdes		if (pos + 1 >= INT_MAX) { \
174181111Sdes			errno = ERANGE; \
175181111Sdes			return -1; \
176181111Sdes		} \
177181111Sdes		if (pos < buflen) \
178181111Sdes			buf[pos] = thechar; \
179181111Sdes		(pos)++; \
180181111Sdes	} while (0)
18198937Sdes
182181111Sdesstatic int dopr(char *buffer, size_t maxlen, const char *format,
183181111Sdes    va_list args_in);
184181111Sdesstatic int fmtstr(char *buffer, size_t *currlen, size_t maxlen,
185181111Sdes    char *value, int flags, int min, int max);
186181111Sdesstatic int fmtint(char *buffer, size_t *currlen, size_t maxlen,
187262566Sdes    intmax_t value, int base, int min, int max, int flags);
188181111Sdesstatic int fmtfp(char *buffer, size_t *currlen, size_t maxlen,
189181111Sdes    LDOUBLE fvalue, int min, int max, int flags);
190181111Sdes
191181111Sdesstatic int
192181111Sdesdopr(char *buffer, size_t maxlen, const char *format, va_list args_in)
19398937Sdes{
194157016Sdes	char ch;
195262566Sdes	intmax_t value;
196157016Sdes	LDOUBLE fvalue;
197157016Sdes	char *strvalue;
198157016Sdes	int min;
199157016Sdes	int max;
200157016Sdes	int state;
201157016Sdes	int flags;
202157016Sdes	int cflags;
203157016Sdes	size_t currlen;
204157016Sdes	va_list args;
205157016Sdes
206157016Sdes	VA_COPY(args, args_in);
207157016Sdes
208157016Sdes	state = DP_S_DEFAULT;
209157016Sdes	currlen = flags = cflags = min = 0;
210157016Sdes	max = -1;
21198937Sdes	ch = *format++;
212157016Sdes
21398937Sdes	while (state != DP_S_DONE) {
214157016Sdes		if (ch == '\0')
21598937Sdes			state = DP_S_DONE;
21698937Sdes
21798937Sdes		switch(state) {
218124208Sdes		case DP_S_DEFAULT:
219124208Sdes			if (ch == '%')
220124208Sdes				state = DP_S_FLAGS;
221181111Sdes			else
222181111Sdes				DOPR_OUTCH(buffer, currlen, maxlen, ch);
223124208Sdes			ch = *format++;
224124208Sdes			break;
225124208Sdes		case DP_S_FLAGS:
226124208Sdes			switch (ch) {
227124208Sdes			case '-':
228124208Sdes				flags |= DP_F_MINUS;
22998937Sdes				ch = *format++;
23098937Sdes				break;
231124208Sdes			case '+':
232124208Sdes				flags |= DP_F_PLUS;
233124208Sdes				ch = *format++;
23498937Sdes				break;
235124208Sdes			case ' ':
236124208Sdes				flags |= DP_F_SPACE;
237124208Sdes				ch = *format++;
23898937Sdes				break;
239124208Sdes			case '#':
240124208Sdes				flags |= DP_F_NUM;
241124208Sdes				ch = *format++;
24298937Sdes				break;
243124208Sdes			case '0':
244124208Sdes				flags |= DP_F_ZERO;
245124208Sdes				ch = *format++;
246124208Sdes				break;
247124208Sdes			default:
248124208Sdes				state = DP_S_MIN;
249124208Sdes				break;
250124208Sdes			}
251124208Sdes			break;
252124208Sdes		case DP_S_MIN:
253124208Sdes			if (isdigit((unsigned char)ch)) {
254157016Sdes				min = 10*min + char_to_int (ch);
255124208Sdes				ch = *format++;
256124208Sdes			} else if (ch == '*') {
257124208Sdes				min = va_arg (args, int);
258124208Sdes				ch = *format++;
259124208Sdes				state = DP_S_DOT;
260157016Sdes			} else {
261124208Sdes				state = DP_S_DOT;
262157016Sdes			}
263124208Sdes			break;
264124208Sdes		case DP_S_DOT:
265124208Sdes			if (ch == '.') {
266124208Sdes				state = DP_S_MAX;
267124208Sdes				ch = *format++;
268157016Sdes			} else {
269124208Sdes				state = DP_S_MOD;
270157016Sdes			}
271124208Sdes			break;
272124208Sdes		case DP_S_MAX:
273124208Sdes			if (isdigit((unsigned char)ch)) {
274124208Sdes				if (max < 0)
275124208Sdes					max = 0;
276157016Sdes				max = 10*max + char_to_int (ch);
277124208Sdes				ch = *format++;
278124208Sdes			} else if (ch == '*') {
279124208Sdes				max = va_arg (args, int);
280124208Sdes				ch = *format++;
281124208Sdes				state = DP_S_MOD;
282157016Sdes			} else {
283124208Sdes				state = DP_S_MOD;
284157016Sdes			}
285124208Sdes			break;
286124208Sdes		case DP_S_MOD:
287124208Sdes			switch (ch) {
288124208Sdes			case 'h':
289124208Sdes				cflags = DP_C_SHORT;
290124208Sdes				ch = *format++;
291124208Sdes				break;
292262566Sdes			case 'j':
293262566Sdes				cflags = DP_C_INTMAX;
294262566Sdes				ch = *format++;
295262566Sdes				break;
296124208Sdes			case 'l':
297124208Sdes				cflags = DP_C_LONG;
298124208Sdes				ch = *format++;
299157016Sdes				if (ch == 'l') {	/* It's a long long */
300157016Sdes					cflags = DP_C_LLONG;
30198937Sdes					ch = *format++;
302124208Sdes				}
30398937Sdes				break;
304124208Sdes			case 'L':
305124208Sdes				cflags = DP_C_LDOUBLE;
306124208Sdes				ch = *format++;
307124208Sdes				break;
308262566Sdes			case 'z':
309262566Sdes				cflags = DP_C_SIZE;
310262566Sdes				ch = *format++;
311262566Sdes				break;
312124208Sdes			default:
313124208Sdes				break;
314124208Sdes			}
315124208Sdes			state = DP_S_CONV;
316124208Sdes			break;
317124208Sdes		case DP_S_CONV:
318124208Sdes			switch (ch) {
319124208Sdes			case 'd':
320124208Sdes			case 'i':
321124208Sdes				if (cflags == DP_C_SHORT)
322157016Sdes					value = va_arg (args, int);
323124208Sdes				else if (cflags == DP_C_LONG)
324157016Sdes					value = va_arg (args, long int);
325157016Sdes				else if (cflags == DP_C_LLONG)
326157016Sdes					value = va_arg (args, LLONG);
327262566Sdes				else if (cflags == DP_C_SIZE)
328262566Sdes					value = va_arg (args, ssize_t);
329262566Sdes				else if (cflags == DP_C_INTMAX)
330262566Sdes					value = va_arg (args, intmax_t);
331124208Sdes				else
332124208Sdes					value = va_arg (args, int);
333181111Sdes				if (fmtint(buffer, &currlen, maxlen,
334181111Sdes				    value, 10, min, max, flags) == -1)
335181111Sdes					return -1;
336124208Sdes				break;
337124208Sdes			case 'o':
338124208Sdes				flags |= DP_F_UNSIGNED;
339124208Sdes				if (cflags == DP_C_SHORT)
340157016Sdes					value = va_arg (args, unsigned int);
341124208Sdes				else if (cflags == DP_C_LONG)
342157016Sdes					value = (long)va_arg (args, unsigned long int);
343157016Sdes				else if (cflags == DP_C_LLONG)
344157016Sdes					value = (long)va_arg (args, unsigned LLONG);
345262566Sdes				else if (cflags == DP_C_SIZE)
346262566Sdes					value = va_arg (args, size_t);
347262566Sdes#ifdef notyet
348262566Sdes				else if (cflags == DP_C_INTMAX)
349262566Sdes					value = va_arg (args, uintmax_t);
350262566Sdes#endif
351124208Sdes				else
352157016Sdes					value = (long)va_arg (args, unsigned int);
353181111Sdes				if (fmtint(buffer, &currlen, maxlen, value,
354181111Sdes				    8, min, max, flags) == -1)
355181111Sdes					return -1;
356124208Sdes				break;
357124208Sdes			case 'u':
358124208Sdes				flags |= DP_F_UNSIGNED;
359124208Sdes				if (cflags == DP_C_SHORT)
360157016Sdes					value = va_arg (args, unsigned int);
361124208Sdes				else if (cflags == DP_C_LONG)
362157016Sdes					value = (long)va_arg (args, unsigned long int);
363157016Sdes				else if (cflags == DP_C_LLONG)
364157016Sdes					value = (LLONG)va_arg (args, unsigned LLONG);
365262566Sdes				else if (cflags == DP_C_SIZE)
366262566Sdes					value = va_arg (args, size_t);
367262566Sdes#ifdef notyet
368262566Sdes				else if (cflags == DP_C_INTMAX)
369262566Sdes					value = va_arg (args, uintmax_t);
370262566Sdes#endif
371124208Sdes				else
372157016Sdes					value = (long)va_arg (args, unsigned int);
373181111Sdes				if (fmtint(buffer, &currlen, maxlen, value,
374181111Sdes				    10, min, max, flags) == -1)
375181111Sdes					return -1;
376124208Sdes				break;
377124208Sdes			case 'X':
378124208Sdes				flags |= DP_F_UP;
379124208Sdes			case 'x':
380124208Sdes				flags |= DP_F_UNSIGNED;
381124208Sdes				if (cflags == DP_C_SHORT)
382157016Sdes					value = va_arg (args, unsigned int);
383124208Sdes				else if (cflags == DP_C_LONG)
384157016Sdes					value = (long)va_arg (args, unsigned long int);
385157016Sdes				else if (cflags == DP_C_LLONG)
386157016Sdes					value = (LLONG)va_arg (args, unsigned LLONG);
387262566Sdes				else if (cflags == DP_C_SIZE)
388262566Sdes					value = va_arg (args, size_t);
389262566Sdes#ifdef notyet
390262566Sdes				else if (cflags == DP_C_INTMAX)
391262566Sdes					value = va_arg (args, uintmax_t);
392262566Sdes#endif
393124208Sdes				else
394157016Sdes					value = (long)va_arg (args, unsigned int);
395181111Sdes				if (fmtint(buffer, &currlen, maxlen, value,
396181111Sdes				    16, min, max, flags) == -1)
397181111Sdes					return -1;
398124208Sdes				break;
399124208Sdes			case 'f':
400124208Sdes				if (cflags == DP_C_LDOUBLE)
401157016Sdes					fvalue = va_arg (args, LDOUBLE);
402124208Sdes				else
403157016Sdes					fvalue = va_arg (args, double);
404181111Sdes				if (fmtfp(buffer, &currlen, maxlen, fvalue,
405181111Sdes				    min, max, flags) == -1)
406181111Sdes					return -1;
407124208Sdes				break;
408124208Sdes			case 'E':
409124208Sdes				flags |= DP_F_UP;
410124208Sdes			case 'e':
411124208Sdes				if (cflags == DP_C_LDOUBLE)
412157016Sdes					fvalue = va_arg (args, LDOUBLE);
413124208Sdes				else
414157016Sdes					fvalue = va_arg (args, double);
415181111Sdes				if (fmtfp(buffer, &currlen, maxlen, fvalue,
416181111Sdes				    min, max, flags) == -1)
417181111Sdes					return -1;
418124208Sdes				break;
419124208Sdes			case 'G':
420124208Sdes				flags |= DP_F_UP;
421124208Sdes			case 'g':
422124208Sdes				if (cflags == DP_C_LDOUBLE)
423157016Sdes					fvalue = va_arg (args, LDOUBLE);
424124208Sdes				else
425157016Sdes					fvalue = va_arg (args, double);
426181111Sdes				if (fmtfp(buffer, &currlen, maxlen, fvalue,
427181111Sdes				    min, max, flags) == -1)
428181111Sdes					return -1;
429124208Sdes				break;
430124208Sdes			case 'c':
431181111Sdes				DOPR_OUTCH(buffer, currlen, maxlen,
432181111Sdes				    va_arg (args, int));
433124208Sdes				break;
434124208Sdes			case 's':
435157016Sdes				strvalue = va_arg (args, char *);
436157016Sdes				if (!strvalue) strvalue = "(NULL)";
437157016Sdes				if (max == -1) {
438157016Sdes					max = strlen(strvalue);
439157016Sdes				}
440157016Sdes				if (min > 0 && max >= 0 && min > max) max = min;
441181111Sdes				if (fmtstr(buffer, &currlen, maxlen,
442181111Sdes				    strvalue, flags, min, max) == -1)
443181111Sdes					return -1;
444124208Sdes				break;
445124208Sdes			case 'p':
446157016Sdes				strvalue = va_arg (args, void *);
447181111Sdes				if (fmtint(buffer, &currlen, maxlen,
448181111Sdes				    (long) strvalue, 16, min, max, flags) == -1)
449181111Sdes					return -1;
450124208Sdes				break;
451262566Sdes#if we_dont_want_this_in_openssh
452124208Sdes			case 'n':
453124208Sdes				if (cflags == DP_C_SHORT) {
454124208Sdes					short int *num;
455157016Sdes					num = va_arg (args, short int *);
456124208Sdes					*num = currlen;
457124208Sdes				} else if (cflags == DP_C_LONG) {
458124208Sdes					long int *num;
459157016Sdes					num = va_arg (args, long int *);
460157016Sdes					*num = (long int)currlen;
461157016Sdes				} else if (cflags == DP_C_LLONG) {
462157016Sdes					LLONG *num;
463157016Sdes					num = va_arg (args, LLONG *);
464157016Sdes					*num = (LLONG)currlen;
465262566Sdes				} else if (cflags == DP_C_SIZE) {
466262566Sdes					ssize_t *num;
467262566Sdes					num = va_arg (args, ssize_t *);
468262566Sdes					*num = (ssize_t)currlen;
469262566Sdes				} else if (cflags == DP_C_INTMAX) {
470262566Sdes					intmax_t *num;
471262566Sdes					num = va_arg (args, intmax_t *);
472262566Sdes					*num = (intmax_t)currlen;
473124208Sdes				} else {
474124208Sdes					int *num;
475157016Sdes					num = va_arg (args, int *);
476124208Sdes					*num = currlen;
47798937Sdes				}
47898937Sdes				break;
479262566Sdes#endif
480124208Sdes			case '%':
481181111Sdes				DOPR_OUTCH(buffer, currlen, maxlen, ch);
482124208Sdes				break;
483157016Sdes			case 'w':
484157016Sdes				/* not supported yet, treat as next char */
48598937Sdes				ch = *format++;
48698937Sdes				break;
487157016Sdes			default:
488157016Sdes				/* Unknown, skip */
489157016Sdes				break;
490124208Sdes			}
491124208Sdes			ch = *format++;
492124208Sdes			state = DP_S_DEFAULT;
493124208Sdes			flags = cflags = min = 0;
494124208Sdes			max = -1;
495124208Sdes			break;
496124208Sdes		case DP_S_DONE:
497124208Sdes			break;
498157016Sdes		default:
499157016Sdes			/* hmm? */
500124208Sdes			break; /* some picky compilers need this */
50198937Sdes		}
50298937Sdes	}
503157016Sdes	if (maxlen != 0) {
504157016Sdes		if (currlen < maxlen - 1)
505157016Sdes			buffer[currlen] = '\0';
506157016Sdes		else if (maxlen > 0)
507157016Sdes			buffer[maxlen - 1] = '\0';
508157016Sdes	}
509157016Sdes
510181111Sdes	return currlen < INT_MAX ? (int)currlen : -1;
51198937Sdes}
51298937Sdes
513181111Sdesstatic int
514181111Sdesfmtstr(char *buffer, size_t *currlen, size_t maxlen,
515181111Sdes    char *value, int flags, int min, int max)
51698937Sdes{
517157016Sdes	int padlen, strln;     /* amount to pad */
518157016Sdes	int cnt = 0;
519157016Sdes
520157016Sdes#ifdef DEBUG_SNPRINTF
521157016Sdes	printf("fmtstr min=%d max=%d s=[%s]\n", min, max, value);
522157016Sdes#endif
523157016Sdes	if (value == 0) {
52498937Sdes		value = "<NULL>";
525157016Sdes	}
52698937Sdes
527146998Sdes	for (strln = 0; strln < max && value[strln]; ++strln); /* strlen */
52898937Sdes	padlen = min - strln;
52998937Sdes	if (padlen < 0)
53098937Sdes		padlen = 0;
53198937Sdes	if (flags & DP_F_MINUS)
53298937Sdes		padlen = -padlen; /* Left Justify */
533157016Sdes
53498937Sdes	while ((padlen > 0) && (cnt < max)) {
535181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, ' ');
53698937Sdes		--padlen;
53798937Sdes		++cnt;
53898937Sdes	}
53998937Sdes	while (*value && (cnt < max)) {
540181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, *value);
541181111Sdes		*value++;
54298937Sdes		++cnt;
54398937Sdes	}
54498937Sdes	while ((padlen < 0) && (cnt < max)) {
545181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, ' ');
54698937Sdes		++padlen;
54798937Sdes		++cnt;
54898937Sdes	}
549181111Sdes	return 0;
55098937Sdes}
55198937Sdes
55298937Sdes/* Have to handle DP_F_NUM (ie 0x and 0 alternates) */
55398937Sdes
554181111Sdesstatic int
555181111Sdesfmtint(char *buffer, size_t *currlen, size_t maxlen,
556162852Sdes		    LLONG value, int base, int min, int max, int flags)
55798937Sdes{
558157016Sdes	int signvalue = 0;
559162852Sdes	unsigned LLONG uvalue;
56098937Sdes	char convert[20];
561157016Sdes	int place = 0;
56298937Sdes	int spadlen = 0; /* amount to space pad */
56398937Sdes	int zpadlen = 0; /* amount to zero pad */
564157016Sdes	int caps = 0;
565157016Sdes
56698937Sdes	if (max < 0)
56798937Sdes		max = 0;
568157016Sdes
56998937Sdes	uvalue = value;
570157016Sdes
571157016Sdes	if(!(flags & DP_F_UNSIGNED)) {
572157016Sdes		if( value < 0 ) {
57398937Sdes			signvalue = '-';
57498937Sdes			uvalue = -value;
575157016Sdes		} else {
576157016Sdes			if (flags & DP_F_PLUS)  /* Do a sign (+/i) */
577157016Sdes				signvalue = '+';
578157016Sdes			else if (flags & DP_F_SPACE)
579157016Sdes				signvalue = ' ';
580157016Sdes		}
58198937Sdes	}
58298937Sdes
583157016Sdes	if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */
584157016Sdes
58598937Sdes	do {
58698937Sdes		convert[place++] =
587157016Sdes			(caps? "0123456789ABCDEF":"0123456789abcdef")
588157016Sdes			[uvalue % (unsigned)base  ];
58998937Sdes		uvalue = (uvalue / (unsigned)base );
590157016Sdes	} while(uvalue && (place < 20));
591157016Sdes	if (place == 20) place--;
59298937Sdes	convert[place] = 0;
59398937Sdes
59498937Sdes	zpadlen = max - place;
59598937Sdes	spadlen = min - MAX (max, place) - (signvalue ? 1 : 0);
596157016Sdes	if (zpadlen < 0) zpadlen = 0;
597157016Sdes	if (spadlen < 0) spadlen = 0;
59898937Sdes	if (flags & DP_F_ZERO) {
59998937Sdes		zpadlen = MAX(zpadlen, spadlen);
60098937Sdes		spadlen = 0;
60198937Sdes	}
60298937Sdes	if (flags & DP_F_MINUS)
60398937Sdes		spadlen = -spadlen; /* Left Justifty */
60498937Sdes
605157016Sdes#ifdef DEBUG_SNPRINTF
606157016Sdes	printf("zpad: %d, spad: %d, min: %d, max: %d, place: %d\n",
607157016Sdes	       zpadlen, spadlen, min, max, place);
608157016Sdes#endif
609157016Sdes
61098937Sdes	/* Spaces */
61198937Sdes	while (spadlen > 0) {
612181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, ' ');
61398937Sdes		--spadlen;
61498937Sdes	}
61598937Sdes
61698937Sdes	/* Sign */
61798937Sdes	if (signvalue)
618181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, signvalue);
61998937Sdes
62098937Sdes	/* Zeros */
62198937Sdes	if (zpadlen > 0) {
62298937Sdes		while (zpadlen > 0) {
623181111Sdes			DOPR_OUTCH(buffer, *currlen, maxlen, '0');
62498937Sdes			--zpadlen;
62598937Sdes		}
62698937Sdes	}
62798937Sdes
62898937Sdes	/* Digits */
629181111Sdes	while (place > 0) {
630181111Sdes		--place;
631181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, convert[place]);
632181111Sdes	}
63398937Sdes
63498937Sdes	/* Left Justified spaces */
63598937Sdes	while (spadlen < 0) {
636181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, ' ');
63798937Sdes		++spadlen;
63898937Sdes	}
639181111Sdes	return 0;
64098937Sdes}
64198937Sdes
642157016Sdesstatic LDOUBLE abs_val(LDOUBLE value)
64398937Sdes{
644157016Sdes	LDOUBLE result = value;
64598937Sdes
646157016Sdes	if (value < 0)
647157016Sdes		result = -value;
648157016Sdes
649157016Sdes	return result;
650157016Sdes}
651157016Sdes
652181111Sdesstatic LDOUBLE POW10(int val)
653157016Sdes{
654157016Sdes	LDOUBLE result = 1;
655157016Sdes
656181111Sdes	while (val) {
65798937Sdes		result *= 10;
658181111Sdes		val--;
65998937Sdes	}
66098937Sdes
66198937Sdes	return result;
66298937Sdes}
66398937Sdes
664157016Sdesstatic LLONG ROUND(LDOUBLE value)
66598937Sdes{
666157016Sdes	LLONG intpart;
66798937Sdes
668157016Sdes	intpart = (LLONG)value;
669157016Sdes	value = value - intpart;
670157016Sdes	if (value >= 0.5) intpart++;
671157016Sdes
67298937Sdes	return intpart;
67398937Sdes}
67498937Sdes
675157016Sdes/* a replacement for modf that doesn't need the math library. Should
676157016Sdes   be portable, but slow */
677157016Sdesstatic double my_modf(double x0, double *iptr)
67898937Sdes{
679157016Sdes	int i;
680157016Sdes	long l;
681157016Sdes	double x = x0;
682157016Sdes	double f = 1.0;
683157016Sdes
684157016Sdes	for (i=0;i<100;i++) {
685157016Sdes		l = (long)x;
686157016Sdes		if (l <= (x+1) && l >= (x-1)) break;
687157016Sdes		x *= 0.1;
688157016Sdes		f *= 10.0;
689157016Sdes	}
690157016Sdes
691157016Sdes	if (i == 100) {
692181111Sdes		/*
693181111Sdes		 * yikes! the number is beyond what we can handle.
694181111Sdes		 * What do we do?
695181111Sdes		 */
696157016Sdes		(*iptr) = 0;
697157016Sdes		return 0;
698157016Sdes	}
699157016Sdes
700157016Sdes	if (i != 0) {
701157016Sdes		double i2;
702157016Sdes		double ret;
703157016Sdes
704157016Sdes		ret = my_modf(x0-l*f, &i2);
705157016Sdes		(*iptr) = l*f + i2;
706157016Sdes		return ret;
707157016Sdes	}
708157016Sdes
709157016Sdes	(*iptr) = l;
710157016Sdes	return x - (*iptr);
711157016Sdes}
712157016Sdes
713157016Sdes
714181111Sdesstatic int
715181111Sdesfmtfp (char *buffer, size_t *currlen, size_t maxlen,
716181111Sdes    LDOUBLE fvalue, int min, int max, int flags)
717157016Sdes{
718157016Sdes	int signvalue = 0;
719157016Sdes	double ufvalue;
720157016Sdes	char iconvert[311];
721157016Sdes	char fconvert[311];
722157016Sdes	int iplace = 0;
723157016Sdes	int fplace = 0;
72498937Sdes	int padlen = 0; /* amount to pad */
725157016Sdes	int zpadlen = 0;
726157016Sdes	int caps = 0;
727157016Sdes	int idx;
728157016Sdes	double intpart;
729157016Sdes	double fracpart;
730157016Sdes	double temp;
73198937Sdes
73298937Sdes	/*
73398937Sdes	 * AIX manpage says the default is 0, but Solaris says the default
73498937Sdes	 * is 6, and sprintf on AIX defaults to 6
73598937Sdes	 */
73698937Sdes	if (max < 0)
73798937Sdes		max = 6;
73898937Sdes
739157016Sdes	ufvalue = abs_val (fvalue);
74098937Sdes
741157016Sdes	if (fvalue < 0) {
74298937Sdes		signvalue = '-';
743157016Sdes	} else {
744157016Sdes		if (flags & DP_F_PLUS) { /* Do a sign (+/i) */
745157016Sdes			signvalue = '+';
746157016Sdes		} else {
747157016Sdes			if (flags & DP_F_SPACE)
748157016Sdes				signvalue = ' ';
749157016Sdes		}
750157016Sdes	}
75198937Sdes
752157016Sdes#if 0
753157016Sdes	if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */
754157016Sdes#endif
75598937Sdes
756157016Sdes#if 0
757157016Sdes	 if (max == 0) ufvalue += 0.5; /* if max = 0 we must round */
758157016Sdes#endif
759157016Sdes
76098937Sdes	/*
761157016Sdes	 * Sorry, we only support 16 digits past the decimal because of our
76298937Sdes	 * conversion method
76398937Sdes	 */
764157016Sdes	if (max > 16)
765157016Sdes		max = 16;
76698937Sdes
76798937Sdes	/* We "cheat" by converting the fractional part to integer by
76898937Sdes	 * multiplying by a factor of 10
76998937Sdes	 */
77098937Sdes
771157016Sdes	temp = ufvalue;
772157016Sdes	my_modf(temp, &intpart);
773157016Sdes
774157016Sdes	fracpart = ROUND((POW10(max)) * (ufvalue - intpart));
775157016Sdes
776157016Sdes	if (fracpart >= POW10(max)) {
77798937Sdes		intpart++;
778157016Sdes		fracpart -= POW10(max);
77998937Sdes	}
78098937Sdes
78198937Sdes	/* Convert integer part */
78298937Sdes	do {
783157016Sdes		temp = intpart*0.1;
784157016Sdes		my_modf(temp, &intpart);
785157016Sdes		idx = (int) ((temp -intpart +0.05)* 10.0);
786157016Sdes		/* idx = (int) (((double)(temp*0.1) -intpart +0.05) *10.0); */
787157016Sdes		/* printf ("%llf, %f, %x\n", temp, intpart, idx); */
78898937Sdes		iconvert[iplace++] =
789157016Sdes			(caps? "0123456789ABCDEF":"0123456789abcdef")[idx];
790157016Sdes	} while (intpart && (iplace < 311));
791157016Sdes	if (iplace == 311) iplace--;
79298937Sdes	iconvert[iplace] = 0;
79398937Sdes
79498937Sdes	/* Convert fractional part */
795157016Sdes	if (fracpart)
796157016Sdes	{
797157016Sdes		do {
798157016Sdes			temp = fracpart*0.1;
799157016Sdes			my_modf(temp, &fracpart);
800157016Sdes			idx = (int) ((temp -fracpart +0.05)* 10.0);
801157016Sdes			/* idx = (int) ((((temp/10) -fracpart) +0.05) *10); */
802157016Sdes			/* printf ("%lf, %lf, %ld\n", temp, fracpart, idx ); */
803157016Sdes			fconvert[fplace++] =
804157016Sdes			(caps? "0123456789ABCDEF":"0123456789abcdef")[idx];
805157016Sdes		} while(fracpart && (fplace < 311));
806157016Sdes		if (fplace == 311) fplace--;
807157016Sdes	}
80898937Sdes	fconvert[fplace] = 0;
809157016Sdes
81098937Sdes	/* -1 for decimal point, another -1 if we are printing a sign */
81198937Sdes	padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0);
81298937Sdes	zpadlen = max - fplace;
813157016Sdes	if (zpadlen < 0) zpadlen = 0;
81498937Sdes	if (padlen < 0)
81598937Sdes		padlen = 0;
81698937Sdes	if (flags & DP_F_MINUS)
81798937Sdes		padlen = -padlen; /* Left Justifty */
818157016Sdes
81998937Sdes	if ((flags & DP_F_ZERO) && (padlen > 0)) {
82098937Sdes		if (signvalue) {
821181111Sdes			DOPR_OUTCH(buffer, *currlen, maxlen, signvalue);
82298937Sdes			--padlen;
82398937Sdes			signvalue = 0;
82498937Sdes		}
82598937Sdes		while (padlen > 0) {
826181111Sdes			DOPR_OUTCH(buffer, *currlen, maxlen, '0');
82798937Sdes			--padlen;
82898937Sdes		}
82998937Sdes	}
83098937Sdes	while (padlen > 0) {
831181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, ' ');
83298937Sdes		--padlen;
83398937Sdes	}
83498937Sdes	if (signvalue)
835181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, signvalue);
836157016Sdes
837181111Sdes	while (iplace > 0) {
838181111Sdes		--iplace;
839181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, iconvert[iplace]);
840181111Sdes	}
84198937Sdes
842157016Sdes#ifdef DEBUG_SNPRINTF
843157016Sdes	printf("fmtfp: fplace=%d zpadlen=%d\n", fplace, zpadlen);
844157016Sdes#endif
845157016Sdes
84698937Sdes	/*
847157016Sdes	 * Decimal point.  This should probably use locale to find the correct
848157016Sdes	 * char to print out.
84998937Sdes	 */
850157016Sdes	if (max > 0) {
851181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, '.');
852157016Sdes
853157016Sdes		while (zpadlen > 0) {
854181111Sdes			DOPR_OUTCH(buffer, *currlen, maxlen, '0');
855157016Sdes			--zpadlen;
856157016Sdes		}
85798937Sdes
858181111Sdes		while (fplace > 0) {
859181111Sdes			--fplace;
860181111Sdes			DOPR_OUTCH(buffer, *currlen, maxlen, fconvert[fplace]);
861181111Sdes		}
86298937Sdes	}
86398937Sdes
86498937Sdes	while (padlen < 0) {
865181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, ' ');
86698937Sdes		++padlen;
86798937Sdes	}
868181111Sdes	return 0;
86998937Sdes}
87098937Sdes#endif /* !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF) */
87198937Sdes
872157016Sdes#if !defined(HAVE_VSNPRINTF)
873181111Sdesint
874181111Sdesvsnprintf (char *str, size_t count, const char *fmt, va_list args)
87598937Sdes{
876157016Sdes	return dopr(str, count, fmt, args);
87798937Sdes}
878157016Sdes#endif
87998937Sdes
880157016Sdes#if !defined(HAVE_SNPRINTF)
881181111Sdesint
882181111Sdessnprintf(char *str, size_t count, SNPRINTF_CONST char *fmt, ...)
88398937Sdes{
884157016Sdes	size_t ret;
88598937Sdes	va_list ap;
88698937Sdes
88798937Sdes	va_start(ap, fmt);
888157016Sdes	ret = vsnprintf(str, count, fmt, ap);
88998937Sdes	va_end(ap);
890157016Sdes	return ret;
89198937Sdes}
892157016Sdes#endif
893