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
10298937Sdes#if !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF)
10398937Sdes
104162852Sdes#include <ctype.h>
105162852Sdes#include <stdarg.h>
106162852Sdes#include <stdlib.h>
107162852Sdes#include <string.h>
108181111Sdes#include <limits.h>
109181111Sdes#include <errno.h>
110162852Sdes
111157016Sdes#ifdef HAVE_LONG_DOUBLE
112157016Sdes# define LDOUBLE long double
113157016Sdes#else
114157016Sdes# define LDOUBLE double
115157016Sdes#endif
11698937Sdes
117157016Sdes#ifdef HAVE_LONG_LONG
118157016Sdes# define LLONG long long
119157016Sdes#else
120157016Sdes# define LLONG long
121157016Sdes#endif
12298937Sdes
12398937Sdes/*
12498937Sdes * dopr(): poor man's version of doprintf
12598937Sdes */
12698937Sdes
12798937Sdes/* format read states */
12898937Sdes#define DP_S_DEFAULT 0
12998937Sdes#define DP_S_FLAGS   1
13098937Sdes#define DP_S_MIN     2
13198937Sdes#define DP_S_DOT     3
13298937Sdes#define DP_S_MAX     4
13398937Sdes#define DP_S_MOD     5
13498937Sdes#define DP_S_CONV    6
13598937Sdes#define DP_S_DONE    7
13698937Sdes
13798937Sdes/* format flags - Bits */
13898937Sdes#define DP_F_MINUS 	(1 << 0)
13998937Sdes#define DP_F_PLUS  	(1 << 1)
14098937Sdes#define DP_F_SPACE 	(1 << 2)
14198937Sdes#define DP_F_NUM   	(1 << 3)
14298937Sdes#define DP_F_ZERO  	(1 << 4)
14398937Sdes#define DP_F_UP    	(1 << 5)
14498937Sdes#define DP_F_UNSIGNED 	(1 << 6)
14598937Sdes
14698937Sdes/* Conversion Flags */
147157016Sdes#define DP_C_SHORT   1
148157016Sdes#define DP_C_LONG    2
149157016Sdes#define DP_C_LDOUBLE 3
150157016Sdes#define DP_C_LLONG   4
151261320Sdes#define DP_C_SIZE    5
152261320Sdes#define DP_C_INTMAX  6
15398937Sdes
154157016Sdes#define char_to_int(p) ((p)- '0')
155157016Sdes#ifndef MAX
156157016Sdes# define MAX(p,q) (((p) >= (q)) ? (p) : (q))
157157016Sdes#endif
15898937Sdes
159181111Sdes#define DOPR_OUTCH(buf, pos, buflen, thechar) \
160181111Sdes	do { \
161181111Sdes		if (pos + 1 >= INT_MAX) { \
162181111Sdes			errno = ERANGE; \
163181111Sdes			return -1; \
164181111Sdes		} \
165181111Sdes		if (pos < buflen) \
166181111Sdes			buf[pos] = thechar; \
167181111Sdes		(pos)++; \
168181111Sdes	} while (0)
16998937Sdes
170181111Sdesstatic int dopr(char *buffer, size_t maxlen, const char *format,
171181111Sdes    va_list args_in);
172181111Sdesstatic int fmtstr(char *buffer, size_t *currlen, size_t maxlen,
173181111Sdes    char *value, int flags, int min, int max);
174181111Sdesstatic int fmtint(char *buffer, size_t *currlen, size_t maxlen,
175261320Sdes    intmax_t value, int base, int min, int max, int flags);
176181111Sdesstatic int fmtfp(char *buffer, size_t *currlen, size_t maxlen,
177181111Sdes    LDOUBLE fvalue, int min, int max, int flags);
178181111Sdes
179181111Sdesstatic int
180181111Sdesdopr(char *buffer, size_t maxlen, const char *format, va_list args_in)
18198937Sdes{
182157016Sdes	char ch;
183261320Sdes	intmax_t value;
184157016Sdes	LDOUBLE fvalue;
185157016Sdes	char *strvalue;
186157016Sdes	int min;
187157016Sdes	int max;
188157016Sdes	int state;
189157016Sdes	int flags;
190157016Sdes	int cflags;
191157016Sdes	size_t currlen;
192157016Sdes	va_list args;
193157016Sdes
194157016Sdes	VA_COPY(args, args_in);
195157016Sdes
196157016Sdes	state = DP_S_DEFAULT;
197157016Sdes	currlen = flags = cflags = min = 0;
198157016Sdes	max = -1;
19998937Sdes	ch = *format++;
200157016Sdes
20198937Sdes	while (state != DP_S_DONE) {
202157016Sdes		if (ch == '\0')
20398937Sdes			state = DP_S_DONE;
20498937Sdes
20598937Sdes		switch(state) {
206124208Sdes		case DP_S_DEFAULT:
207124208Sdes			if (ch == '%')
208124208Sdes				state = DP_S_FLAGS;
209181111Sdes			else
210181111Sdes				DOPR_OUTCH(buffer, currlen, maxlen, ch);
211124208Sdes			ch = *format++;
212124208Sdes			break;
213124208Sdes		case DP_S_FLAGS:
214124208Sdes			switch (ch) {
215124208Sdes			case '-':
216124208Sdes				flags |= DP_F_MINUS;
21798937Sdes				ch = *format++;
21898937Sdes				break;
219124208Sdes			case '+':
220124208Sdes				flags |= DP_F_PLUS;
221124208Sdes				ch = *format++;
22298937Sdes				break;
223124208Sdes			case ' ':
224124208Sdes				flags |= DP_F_SPACE;
225124208Sdes				ch = *format++;
22698937Sdes				break;
227124208Sdes			case '#':
228124208Sdes				flags |= DP_F_NUM;
229124208Sdes				ch = *format++;
23098937Sdes				break;
231124208Sdes			case '0':
232124208Sdes				flags |= DP_F_ZERO;
233124208Sdes				ch = *format++;
234124208Sdes				break;
235124208Sdes			default:
236124208Sdes				state = DP_S_MIN;
237124208Sdes				break;
238124208Sdes			}
239124208Sdes			break;
240124208Sdes		case DP_S_MIN:
241124208Sdes			if (isdigit((unsigned char)ch)) {
242157016Sdes				min = 10*min + char_to_int (ch);
243124208Sdes				ch = *format++;
244124208Sdes			} else if (ch == '*') {
245124208Sdes				min = va_arg (args, int);
246124208Sdes				ch = *format++;
247124208Sdes				state = DP_S_DOT;
248157016Sdes			} else {
249124208Sdes				state = DP_S_DOT;
250157016Sdes			}
251124208Sdes			break;
252124208Sdes		case DP_S_DOT:
253124208Sdes			if (ch == '.') {
254124208Sdes				state = DP_S_MAX;
255124208Sdes				ch = *format++;
256157016Sdes			} else {
257124208Sdes				state = DP_S_MOD;
258157016Sdes			}
259124208Sdes			break;
260124208Sdes		case DP_S_MAX:
261124208Sdes			if (isdigit((unsigned char)ch)) {
262124208Sdes				if (max < 0)
263124208Sdes					max = 0;
264157016Sdes				max = 10*max + char_to_int (ch);
265124208Sdes				ch = *format++;
266124208Sdes			} else if (ch == '*') {
267124208Sdes				max = va_arg (args, int);
268124208Sdes				ch = *format++;
269124208Sdes				state = DP_S_MOD;
270157016Sdes			} else {
271124208Sdes				state = DP_S_MOD;
272157016Sdes			}
273124208Sdes			break;
274124208Sdes		case DP_S_MOD:
275124208Sdes			switch (ch) {
276124208Sdes			case 'h':
277124208Sdes				cflags = DP_C_SHORT;
278124208Sdes				ch = *format++;
279124208Sdes				break;
280261320Sdes			case 'j':
281261320Sdes				cflags = DP_C_INTMAX;
282261320Sdes				ch = *format++;
283261320Sdes				break;
284124208Sdes			case 'l':
285124208Sdes				cflags = DP_C_LONG;
286124208Sdes				ch = *format++;
287157016Sdes				if (ch == 'l') {	/* It's a long long */
288157016Sdes					cflags = DP_C_LLONG;
28998937Sdes					ch = *format++;
290124208Sdes				}
29198937Sdes				break;
292124208Sdes			case 'L':
293124208Sdes				cflags = DP_C_LDOUBLE;
294124208Sdes				ch = *format++;
295124208Sdes				break;
296261320Sdes			case 'z':
297261320Sdes				cflags = DP_C_SIZE;
298261320Sdes				ch = *format++;
299261320Sdes				break;
300124208Sdes			default:
301124208Sdes				break;
302124208Sdes			}
303124208Sdes			state = DP_S_CONV;
304124208Sdes			break;
305124208Sdes		case DP_S_CONV:
306124208Sdes			switch (ch) {
307124208Sdes			case 'd':
308124208Sdes			case 'i':
309124208Sdes				if (cflags == DP_C_SHORT)
310157016Sdes					value = va_arg (args, int);
311124208Sdes				else if (cflags == DP_C_LONG)
312157016Sdes					value = va_arg (args, long int);
313157016Sdes				else if (cflags == DP_C_LLONG)
314157016Sdes					value = va_arg (args, LLONG);
315261320Sdes				else if (cflags == DP_C_SIZE)
316261320Sdes					value = va_arg (args, ssize_t);
317261320Sdes				else if (cflags == DP_C_INTMAX)
318261320Sdes					value = va_arg (args, intmax_t);
319124208Sdes				else
320124208Sdes					value = va_arg (args, int);
321181111Sdes				if (fmtint(buffer, &currlen, maxlen,
322181111Sdes				    value, 10, min, max, flags) == -1)
323181111Sdes					return -1;
324124208Sdes				break;
325124208Sdes			case 'o':
326124208Sdes				flags |= DP_F_UNSIGNED;
327124208Sdes				if (cflags == DP_C_SHORT)
328157016Sdes					value = va_arg (args, unsigned int);
329124208Sdes				else if (cflags == DP_C_LONG)
330157016Sdes					value = (long)va_arg (args, unsigned long int);
331157016Sdes				else if (cflags == DP_C_LLONG)
332157016Sdes					value = (long)va_arg (args, unsigned LLONG);
333261320Sdes				else if (cflags == DP_C_SIZE)
334261320Sdes					value = va_arg (args, size_t);
335261320Sdes#ifdef notyet
336261320Sdes				else if (cflags == DP_C_INTMAX)
337261320Sdes					value = va_arg (args, uintmax_t);
338261320Sdes#endif
339124208Sdes				else
340157016Sdes					value = (long)va_arg (args, unsigned int);
341181111Sdes				if (fmtint(buffer, &currlen, maxlen, value,
342181111Sdes				    8, min, max, flags) == -1)
343181111Sdes					return -1;
344124208Sdes				break;
345124208Sdes			case 'u':
346124208Sdes				flags |= DP_F_UNSIGNED;
347124208Sdes				if (cflags == DP_C_SHORT)
348157016Sdes					value = va_arg (args, unsigned int);
349124208Sdes				else if (cflags == DP_C_LONG)
350157016Sdes					value = (long)va_arg (args, unsigned long int);
351157016Sdes				else if (cflags == DP_C_LLONG)
352157016Sdes					value = (LLONG)va_arg (args, unsigned LLONG);
353261320Sdes				else if (cflags == DP_C_SIZE)
354261320Sdes					value = va_arg (args, size_t);
355261320Sdes#ifdef notyet
356261320Sdes				else if (cflags == DP_C_INTMAX)
357261320Sdes					value = va_arg (args, uintmax_t);
358261320Sdes#endif
359124208Sdes				else
360157016Sdes					value = (long)va_arg (args, unsigned int);
361181111Sdes				if (fmtint(buffer, &currlen, maxlen, value,
362181111Sdes				    10, min, max, flags) == -1)
363181111Sdes					return -1;
364124208Sdes				break;
365124208Sdes			case 'X':
366124208Sdes				flags |= DP_F_UP;
367124208Sdes			case 'x':
368124208Sdes				flags |= DP_F_UNSIGNED;
369124208Sdes				if (cflags == DP_C_SHORT)
370157016Sdes					value = va_arg (args, unsigned int);
371124208Sdes				else if (cflags == DP_C_LONG)
372157016Sdes					value = (long)va_arg (args, unsigned long int);
373157016Sdes				else if (cflags == DP_C_LLONG)
374157016Sdes					value = (LLONG)va_arg (args, unsigned LLONG);
375261320Sdes				else if (cflags == DP_C_SIZE)
376261320Sdes					value = va_arg (args, size_t);
377261320Sdes#ifdef notyet
378261320Sdes				else if (cflags == DP_C_INTMAX)
379261320Sdes					value = va_arg (args, uintmax_t);
380261320Sdes#endif
381124208Sdes				else
382157016Sdes					value = (long)va_arg (args, unsigned int);
383181111Sdes				if (fmtint(buffer, &currlen, maxlen, value,
384181111Sdes				    16, min, max, flags) == -1)
385181111Sdes					return -1;
386124208Sdes				break;
387124208Sdes			case 'f':
388124208Sdes				if (cflags == DP_C_LDOUBLE)
389157016Sdes					fvalue = va_arg (args, LDOUBLE);
390124208Sdes				else
391157016Sdes					fvalue = va_arg (args, double);
392181111Sdes				if (fmtfp(buffer, &currlen, maxlen, fvalue,
393181111Sdes				    min, max, flags) == -1)
394181111Sdes					return -1;
395124208Sdes				break;
396124208Sdes			case 'E':
397124208Sdes				flags |= DP_F_UP;
398124208Sdes			case 'e':
399124208Sdes				if (cflags == DP_C_LDOUBLE)
400157016Sdes					fvalue = va_arg (args, LDOUBLE);
401124208Sdes				else
402157016Sdes					fvalue = va_arg (args, double);
403181111Sdes				if (fmtfp(buffer, &currlen, maxlen, fvalue,
404181111Sdes				    min, max, flags) == -1)
405181111Sdes					return -1;
406124208Sdes				break;
407124208Sdes			case 'G':
408124208Sdes				flags |= DP_F_UP;
409124208Sdes			case 'g':
410124208Sdes				if (cflags == DP_C_LDOUBLE)
411157016Sdes					fvalue = va_arg (args, LDOUBLE);
412124208Sdes				else
413157016Sdes					fvalue = va_arg (args, double);
414181111Sdes				if (fmtfp(buffer, &currlen, maxlen, fvalue,
415181111Sdes				    min, max, flags) == -1)
416181111Sdes					return -1;
417124208Sdes				break;
418124208Sdes			case 'c':
419181111Sdes				DOPR_OUTCH(buffer, currlen, maxlen,
420181111Sdes				    va_arg (args, int));
421124208Sdes				break;
422124208Sdes			case 's':
423157016Sdes				strvalue = va_arg (args, char *);
424157016Sdes				if (!strvalue) strvalue = "(NULL)";
425157016Sdes				if (max == -1) {
426157016Sdes					max = strlen(strvalue);
427157016Sdes				}
428157016Sdes				if (min > 0 && max >= 0 && min > max) max = min;
429181111Sdes				if (fmtstr(buffer, &currlen, maxlen,
430181111Sdes				    strvalue, flags, min, max) == -1)
431181111Sdes					return -1;
432124208Sdes				break;
433124208Sdes			case 'p':
434157016Sdes				strvalue = va_arg (args, void *);
435181111Sdes				if (fmtint(buffer, &currlen, maxlen,
436181111Sdes				    (long) strvalue, 16, min, max, flags) == -1)
437181111Sdes					return -1;
438124208Sdes				break;
439261320Sdes#if we_dont_want_this_in_openssh
440124208Sdes			case 'n':
441124208Sdes				if (cflags == DP_C_SHORT) {
442124208Sdes					short int *num;
443157016Sdes					num = va_arg (args, short int *);
444124208Sdes					*num = currlen;
445124208Sdes				} else if (cflags == DP_C_LONG) {
446124208Sdes					long int *num;
447157016Sdes					num = va_arg (args, long int *);
448157016Sdes					*num = (long int)currlen;
449157016Sdes				} else if (cflags == DP_C_LLONG) {
450157016Sdes					LLONG *num;
451157016Sdes					num = va_arg (args, LLONG *);
452157016Sdes					*num = (LLONG)currlen;
453261320Sdes				} else if (cflags == DP_C_SIZE) {
454261320Sdes					ssize_t *num;
455261320Sdes					num = va_arg (args, ssize_t *);
456261320Sdes					*num = (ssize_t)currlen;
457261320Sdes				} else if (cflags == DP_C_INTMAX) {
458261320Sdes					intmax_t *num;
459261320Sdes					num = va_arg (args, intmax_t *);
460261320Sdes					*num = (intmax_t)currlen;
461124208Sdes				} else {
462124208Sdes					int *num;
463157016Sdes					num = va_arg (args, int *);
464124208Sdes					*num = currlen;
46598937Sdes				}
46698937Sdes				break;
467261320Sdes#endif
468124208Sdes			case '%':
469181111Sdes				DOPR_OUTCH(buffer, currlen, maxlen, ch);
470124208Sdes				break;
471157016Sdes			case 'w':
472157016Sdes				/* not supported yet, treat as next char */
47398937Sdes				ch = *format++;
47498937Sdes				break;
475157016Sdes			default:
476157016Sdes				/* Unknown, skip */
477157016Sdes				break;
478124208Sdes			}
479124208Sdes			ch = *format++;
480124208Sdes			state = DP_S_DEFAULT;
481124208Sdes			flags = cflags = min = 0;
482124208Sdes			max = -1;
483124208Sdes			break;
484124208Sdes		case DP_S_DONE:
485124208Sdes			break;
486157016Sdes		default:
487157016Sdes			/* hmm? */
488124208Sdes			break; /* some picky compilers need this */
48998937Sdes		}
49098937Sdes	}
491157016Sdes	if (maxlen != 0) {
492157016Sdes		if (currlen < maxlen - 1)
493157016Sdes			buffer[currlen] = '\0';
494157016Sdes		else if (maxlen > 0)
495157016Sdes			buffer[maxlen - 1] = '\0';
496157016Sdes	}
497157016Sdes
498181111Sdes	return currlen < INT_MAX ? (int)currlen : -1;
49998937Sdes}
50098937Sdes
501181111Sdesstatic int
502181111Sdesfmtstr(char *buffer, size_t *currlen, size_t maxlen,
503181111Sdes    char *value, int flags, int min, int max)
50498937Sdes{
505157016Sdes	int padlen, strln;     /* amount to pad */
506157016Sdes	int cnt = 0;
507157016Sdes
508157016Sdes#ifdef DEBUG_SNPRINTF
509157016Sdes	printf("fmtstr min=%d max=%d s=[%s]\n", min, max, value);
510157016Sdes#endif
511157016Sdes	if (value == 0) {
51298937Sdes		value = "<NULL>";
513157016Sdes	}
51498937Sdes
515146998Sdes	for (strln = 0; strln < max && value[strln]; ++strln); /* strlen */
51698937Sdes	padlen = min - strln;
51798937Sdes	if (padlen < 0)
51898937Sdes		padlen = 0;
51998937Sdes	if (flags & DP_F_MINUS)
52098937Sdes		padlen = -padlen; /* Left Justify */
521157016Sdes
52298937Sdes	while ((padlen > 0) && (cnt < max)) {
523181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, ' ');
52498937Sdes		--padlen;
52598937Sdes		++cnt;
52698937Sdes	}
52798937Sdes	while (*value && (cnt < max)) {
528181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, *value);
529294328Sdes		value++;
53098937Sdes		++cnt;
53198937Sdes	}
53298937Sdes	while ((padlen < 0) && (cnt < max)) {
533181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, ' ');
53498937Sdes		++padlen;
53598937Sdes		++cnt;
53698937Sdes	}
537181111Sdes	return 0;
53898937Sdes}
53998937Sdes
54098937Sdes/* Have to handle DP_F_NUM (ie 0x and 0 alternates) */
54198937Sdes
542181111Sdesstatic int
543181111Sdesfmtint(char *buffer, size_t *currlen, size_t maxlen,
544294328Sdes    intmax_t value, int base, int min, int max, int flags)
54598937Sdes{
546157016Sdes	int signvalue = 0;
547162852Sdes	unsigned LLONG uvalue;
54898937Sdes	char convert[20];
549157016Sdes	int place = 0;
55098937Sdes	int spadlen = 0; /* amount to space pad */
55198937Sdes	int zpadlen = 0; /* amount to zero pad */
552157016Sdes	int caps = 0;
553157016Sdes
55498937Sdes	if (max < 0)
55598937Sdes		max = 0;
556157016Sdes
55798937Sdes	uvalue = value;
558157016Sdes
559157016Sdes	if(!(flags & DP_F_UNSIGNED)) {
560157016Sdes		if( value < 0 ) {
56198937Sdes			signvalue = '-';
56298937Sdes			uvalue = -value;
563157016Sdes		} else {
564157016Sdes			if (flags & DP_F_PLUS)  /* Do a sign (+/i) */
565157016Sdes				signvalue = '+';
566157016Sdes			else if (flags & DP_F_SPACE)
567157016Sdes				signvalue = ' ';
568157016Sdes		}
56998937Sdes	}
57098937Sdes
571157016Sdes	if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */
572157016Sdes
57398937Sdes	do {
57498937Sdes		convert[place++] =
575157016Sdes			(caps? "0123456789ABCDEF":"0123456789abcdef")
576157016Sdes			[uvalue % (unsigned)base  ];
57798937Sdes		uvalue = (uvalue / (unsigned)base );
578157016Sdes	} while(uvalue && (place < 20));
579157016Sdes	if (place == 20) place--;
58098937Sdes	convert[place] = 0;
58198937Sdes
58298937Sdes	zpadlen = max - place;
58398937Sdes	spadlen = min - MAX (max, place) - (signvalue ? 1 : 0);
584157016Sdes	if (zpadlen < 0) zpadlen = 0;
585157016Sdes	if (spadlen < 0) spadlen = 0;
58698937Sdes	if (flags & DP_F_ZERO) {
58798937Sdes		zpadlen = MAX(zpadlen, spadlen);
58898937Sdes		spadlen = 0;
58998937Sdes	}
59098937Sdes	if (flags & DP_F_MINUS)
59198937Sdes		spadlen = -spadlen; /* Left Justifty */
59298937Sdes
593157016Sdes#ifdef DEBUG_SNPRINTF
594157016Sdes	printf("zpad: %d, spad: %d, min: %d, max: %d, place: %d\n",
595157016Sdes	       zpadlen, spadlen, min, max, place);
596157016Sdes#endif
597157016Sdes
59898937Sdes	/* Spaces */
59998937Sdes	while (spadlen > 0) {
600181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, ' ');
60198937Sdes		--spadlen;
60298937Sdes	}
60398937Sdes
60498937Sdes	/* Sign */
60598937Sdes	if (signvalue)
606181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, signvalue);
60798937Sdes
60898937Sdes	/* Zeros */
60998937Sdes	if (zpadlen > 0) {
61098937Sdes		while (zpadlen > 0) {
611181111Sdes			DOPR_OUTCH(buffer, *currlen, maxlen, '0');
61298937Sdes			--zpadlen;
61398937Sdes		}
61498937Sdes	}
61598937Sdes
61698937Sdes	/* Digits */
617181111Sdes	while (place > 0) {
618181111Sdes		--place;
619181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, convert[place]);
620181111Sdes	}
62198937Sdes
62298937Sdes	/* Left Justified spaces */
62398937Sdes	while (spadlen < 0) {
624181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, ' ');
62598937Sdes		++spadlen;
62698937Sdes	}
627181111Sdes	return 0;
62898937Sdes}
62998937Sdes
630157016Sdesstatic LDOUBLE abs_val(LDOUBLE value)
63198937Sdes{
632157016Sdes	LDOUBLE result = value;
63398937Sdes
634157016Sdes	if (value < 0)
635157016Sdes		result = -value;
636157016Sdes
637157016Sdes	return result;
638157016Sdes}
639157016Sdes
640181111Sdesstatic LDOUBLE POW10(int val)
641157016Sdes{
642157016Sdes	LDOUBLE result = 1;
643157016Sdes
644181111Sdes	while (val) {
64598937Sdes		result *= 10;
646181111Sdes		val--;
64798937Sdes	}
64898937Sdes
64998937Sdes	return result;
65098937Sdes}
65198937Sdes
652157016Sdesstatic LLONG ROUND(LDOUBLE value)
65398937Sdes{
654157016Sdes	LLONG intpart;
65598937Sdes
656157016Sdes	intpart = (LLONG)value;
657157016Sdes	value = value - intpart;
658157016Sdes	if (value >= 0.5) intpart++;
659157016Sdes
66098937Sdes	return intpart;
66198937Sdes}
66298937Sdes
663157016Sdes/* a replacement for modf that doesn't need the math library. Should
664157016Sdes   be portable, but slow */
665157016Sdesstatic double my_modf(double x0, double *iptr)
66698937Sdes{
667157016Sdes	int i;
668157016Sdes	long l;
669157016Sdes	double x = x0;
670157016Sdes	double f = 1.0;
671157016Sdes
672157016Sdes	for (i=0;i<100;i++) {
673157016Sdes		l = (long)x;
674157016Sdes		if (l <= (x+1) && l >= (x-1)) break;
675157016Sdes		x *= 0.1;
676157016Sdes		f *= 10.0;
677157016Sdes	}
678157016Sdes
679157016Sdes	if (i == 100) {
680181111Sdes		/*
681181111Sdes		 * yikes! the number is beyond what we can handle.
682181111Sdes		 * What do we do?
683181111Sdes		 */
684157016Sdes		(*iptr) = 0;
685157016Sdes		return 0;
686157016Sdes	}
687157016Sdes
688157016Sdes	if (i != 0) {
689157016Sdes		double i2;
690157016Sdes		double ret;
691157016Sdes
692157016Sdes		ret = my_modf(x0-l*f, &i2);
693157016Sdes		(*iptr) = l*f + i2;
694157016Sdes		return ret;
695157016Sdes	}
696157016Sdes
697157016Sdes	(*iptr) = l;
698157016Sdes	return x - (*iptr);
699157016Sdes}
700157016Sdes
701157016Sdes
702181111Sdesstatic int
703181111Sdesfmtfp (char *buffer, size_t *currlen, size_t maxlen,
704181111Sdes    LDOUBLE fvalue, int min, int max, int flags)
705157016Sdes{
706157016Sdes	int signvalue = 0;
707157016Sdes	double ufvalue;
708157016Sdes	char iconvert[311];
709157016Sdes	char fconvert[311];
710157016Sdes	int iplace = 0;
711157016Sdes	int fplace = 0;
71298937Sdes	int padlen = 0; /* amount to pad */
713157016Sdes	int zpadlen = 0;
714157016Sdes	int caps = 0;
715157016Sdes	int idx;
716157016Sdes	double intpart;
717157016Sdes	double fracpart;
718157016Sdes	double temp;
71998937Sdes
72098937Sdes	/*
72198937Sdes	 * AIX manpage says the default is 0, but Solaris says the default
72298937Sdes	 * is 6, and sprintf on AIX defaults to 6
72398937Sdes	 */
72498937Sdes	if (max < 0)
72598937Sdes		max = 6;
72698937Sdes
727157016Sdes	ufvalue = abs_val (fvalue);
72898937Sdes
729157016Sdes	if (fvalue < 0) {
73098937Sdes		signvalue = '-';
731157016Sdes	} else {
732157016Sdes		if (flags & DP_F_PLUS) { /* Do a sign (+/i) */
733157016Sdes			signvalue = '+';
734157016Sdes		} else {
735157016Sdes			if (flags & DP_F_SPACE)
736157016Sdes				signvalue = ' ';
737157016Sdes		}
738157016Sdes	}
73998937Sdes
740157016Sdes#if 0
741157016Sdes	if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */
742157016Sdes#endif
74398937Sdes
744157016Sdes#if 0
745157016Sdes	 if (max == 0) ufvalue += 0.5; /* if max = 0 we must round */
746157016Sdes#endif
747157016Sdes
74898937Sdes	/*
749157016Sdes	 * Sorry, we only support 16 digits past the decimal because of our
75098937Sdes	 * conversion method
75198937Sdes	 */
752157016Sdes	if (max > 16)
753157016Sdes		max = 16;
75498937Sdes
75598937Sdes	/* We "cheat" by converting the fractional part to integer by
75698937Sdes	 * multiplying by a factor of 10
75798937Sdes	 */
75898937Sdes
759157016Sdes	temp = ufvalue;
760157016Sdes	my_modf(temp, &intpart);
761157016Sdes
762157016Sdes	fracpart = ROUND((POW10(max)) * (ufvalue - intpart));
763157016Sdes
764157016Sdes	if (fracpart >= POW10(max)) {
76598937Sdes		intpart++;
766157016Sdes		fracpart -= POW10(max);
76798937Sdes	}
76898937Sdes
76998937Sdes	/* Convert integer part */
77098937Sdes	do {
771157016Sdes		temp = intpart*0.1;
772157016Sdes		my_modf(temp, &intpart);
773157016Sdes		idx = (int) ((temp -intpart +0.05)* 10.0);
774157016Sdes		/* idx = (int) (((double)(temp*0.1) -intpart +0.05) *10.0); */
775157016Sdes		/* printf ("%llf, %f, %x\n", temp, intpart, idx); */
77698937Sdes		iconvert[iplace++] =
777157016Sdes			(caps? "0123456789ABCDEF":"0123456789abcdef")[idx];
778157016Sdes	} while (intpart && (iplace < 311));
779157016Sdes	if (iplace == 311) iplace--;
78098937Sdes	iconvert[iplace] = 0;
78198937Sdes
78298937Sdes	/* Convert fractional part */
783157016Sdes	if (fracpart)
784157016Sdes	{
785157016Sdes		do {
786157016Sdes			temp = fracpart*0.1;
787157016Sdes			my_modf(temp, &fracpart);
788157016Sdes			idx = (int) ((temp -fracpart +0.05)* 10.0);
789157016Sdes			/* idx = (int) ((((temp/10) -fracpart) +0.05) *10); */
790157016Sdes			/* printf ("%lf, %lf, %ld\n", temp, fracpart, idx ); */
791157016Sdes			fconvert[fplace++] =
792157016Sdes			(caps? "0123456789ABCDEF":"0123456789abcdef")[idx];
793157016Sdes		} while(fracpart && (fplace < 311));
794157016Sdes		if (fplace == 311) fplace--;
795157016Sdes	}
79698937Sdes	fconvert[fplace] = 0;
797157016Sdes
79898937Sdes	/* -1 for decimal point, another -1 if we are printing a sign */
79998937Sdes	padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0);
80098937Sdes	zpadlen = max - fplace;
801157016Sdes	if (zpadlen < 0) zpadlen = 0;
80298937Sdes	if (padlen < 0)
80398937Sdes		padlen = 0;
80498937Sdes	if (flags & DP_F_MINUS)
80598937Sdes		padlen = -padlen; /* Left Justifty */
806157016Sdes
80798937Sdes	if ((flags & DP_F_ZERO) && (padlen > 0)) {
80898937Sdes		if (signvalue) {
809181111Sdes			DOPR_OUTCH(buffer, *currlen, maxlen, signvalue);
81098937Sdes			--padlen;
81198937Sdes			signvalue = 0;
81298937Sdes		}
81398937Sdes		while (padlen > 0) {
814181111Sdes			DOPR_OUTCH(buffer, *currlen, maxlen, '0');
81598937Sdes			--padlen;
81698937Sdes		}
81798937Sdes	}
81898937Sdes	while (padlen > 0) {
819181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, ' ');
82098937Sdes		--padlen;
82198937Sdes	}
82298937Sdes	if (signvalue)
823181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, signvalue);
824157016Sdes
825181111Sdes	while (iplace > 0) {
826181111Sdes		--iplace;
827181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, iconvert[iplace]);
828181111Sdes	}
82998937Sdes
830157016Sdes#ifdef DEBUG_SNPRINTF
831157016Sdes	printf("fmtfp: fplace=%d zpadlen=%d\n", fplace, zpadlen);
832157016Sdes#endif
833157016Sdes
83498937Sdes	/*
835157016Sdes	 * Decimal point.  This should probably use locale to find the correct
836157016Sdes	 * char to print out.
83798937Sdes	 */
838157016Sdes	if (max > 0) {
839181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, '.');
840157016Sdes
841157016Sdes		while (zpadlen > 0) {
842181111Sdes			DOPR_OUTCH(buffer, *currlen, maxlen, '0');
843157016Sdes			--zpadlen;
844157016Sdes		}
84598937Sdes
846181111Sdes		while (fplace > 0) {
847181111Sdes			--fplace;
848181111Sdes			DOPR_OUTCH(buffer, *currlen, maxlen, fconvert[fplace]);
849181111Sdes		}
85098937Sdes	}
85198937Sdes
85298937Sdes	while (padlen < 0) {
853181111Sdes		DOPR_OUTCH(buffer, *currlen, maxlen, ' ');
85498937Sdes		++padlen;
85598937Sdes	}
856181111Sdes	return 0;
85798937Sdes}
85898937Sdes#endif /* !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF) */
85998937Sdes
860157016Sdes#if !defined(HAVE_VSNPRINTF)
861181111Sdesint
862181111Sdesvsnprintf (char *str, size_t count, const char *fmt, va_list args)
86398937Sdes{
864157016Sdes	return dopr(str, count, fmt, args);
86598937Sdes}
866157016Sdes#endif
86798937Sdes
868157016Sdes#if !defined(HAVE_SNPRINTF)
869181111Sdesint
870181111Sdessnprintf(char *str, size_t count, SNPRINTF_CONST char *fmt, ...)
87198937Sdes{
872157016Sdes	size_t ret;
87398937Sdes	va_list ap;
87498937Sdes
87598937Sdes	va_start(ap, fmt);
876157016Sdes	ret = vsnprintf(str, count, fmt, ap);
87798937Sdes	va_end(ap);
878157016Sdes	return ret;
87998937Sdes}
880157016Sdes#endif
881