/*
 * LIB/SNPRINTF.C - snprintf module if we don't have it
 *
 * 	snprintf() is just too important, we MUST have it, so if the OS
 *	doesn't support this module will include it.  Unfortunately, to
 *	do it right also required me to write an equivalent pfmt(), so
 *	all the snprintf()'s in the code support only a small subset
 *	of valid '%' escapes:
 *
 *		%[-][0][n][l]d
 *		%[-][0][n][l]u
 *		%[-][0][n][l]x
 *		%[-][0][n]c
 *		%[-][n]s
 *
 * NOTE!!!!! this snprintf only supports %x, %d, %c, and %s
 *
 * (c)Copyright 1998, Matthew Dillon, All Rights Reserved.  Refer to
 *    the COPYRIGHT file in the base directory of this distribution
 *    for specific rights granted.
 */

#include "defs.h"

int MiniPfmt(const char *ctl, va_list va, void (*callback)(void *info, const char *buf, int bytes), void *info);

#ifdef TEST
#undef HAVE_SNPRINTF
#define HAVE_SNPRINTF	0
void fatal(const char *ctl, ...);
#endif

#if USE_INTERNAL_SNPRINTF
#undef HAVE_SNPRINTF
#define HAVE_SNPRINTF	0
#endif

#if HAVE_SNPRINTF == 0

/*
 * no prototypes, snprintf is extern'd in lib/defs.h since it may 
 * be conditionally compiled.
 */

int 
snprintf(char *str, size_t size, const char *ctl, ...)
{
    va_list va;
    int r;

    va_start(va, ctl);
    r = vsnprintf(str, size, ctl, va);
    va_end(va);
    return(r);
}

typedef struct SBuf {
    char *sb_Buf;
    int  sb_Len;
} SBuf;

void
vsncallback(void *info, const char *buf, int bytes)
{
    SBuf *sbuf = info;
    if (bytes > sbuf->sb_Len)
	bytes = sbuf->sb_Len;
    if (bytes) {
	memcpy(sbuf->sb_Buf, buf, bytes);
	sbuf->sb_Buf += bytes;
	sbuf->sb_Len -= bytes;
    }
}

int 
vsnprintf(char *str, size_t size, const char *ctl, va_list va)
{
    SBuf sbuf;
    int r;

    sbuf.sb_Buf = str;
    sbuf.sb_Len = size - 1;
    r = MiniPfmt(ctl, va, vsncallback, &sbuf);
    *sbuf.sb_Buf = 0;

    if (r > size)
	r = size;	
    return(r);
}

#define FMT_LONG	0x01
#define FMT_LEFT	0x02
#define FMT_ZFILL	0x04
#define FMT_RIGHT	0x08
#define FMT_ISNEG	0x10

int
MiniPfmt(const char *ctl, va_list va, void (*callback)(void *info, const char *buf, int bytes), void *info)
{
    int r;
    int i;
    int b;

    r = 0;

    for (i = b = 0; ctl[i]; ++i) {
	int flags;
	int fwidth;
	char tbuf[32];
	char *tptr;
	int tlen;

	if (ctl[i] != '%')
	    continue;
	if (i != b) {
	    callback(info, ctl + b, i - b);
	    r += i - b;
	}

	flags = FMT_RIGHT;
	fwidth = 0;	/* exact fit	*/

	/*
	 * pointer to temporary buffer (for numbers),
	 * one past so we can put a minus sign before
	 * the beginning later on.
	 */

	tptr = tbuf + 1;
	tlen = 0;

	for (++i; ;++i) {
	    char c = ctl[i];

	    if (c == 0) {
		fatal("MiniPfmt: unterminated % escape in %s", ctl);
		/* not reached */
	    }
	    if (c == 's') {	/* string  */
		 tptr = va_arg(va, char *);
		 tlen = strlen(tptr);
		 break;
	    }
	    if (c == 'd' || c == 'u') {	/* decimal */
		long v;

		if (flags & FMT_LONG)
		    v = va_arg(va, long);
		else
		    v = va_arg(va, int);

		if (c == 'd' && v < 0) {
		    v = -v;
		    flags |= FMT_ISNEG;
		}
		sprintf(tptr, "%lu", (unsigned long)v);
		tlen = strlen(tptr);
		break;
	    }
	    if (c == 'x') {	/* hex	   */
		if (flags & FMT_LONG) {
		    long v = va_arg(va, long);
		    sprintf(tptr, "%lx", v);
		} else {
		    int v = va_arg(va, int);
		    sprintf(tptr, "%x", v);
		}
		tlen = strlen(tptr);
		break;
	    }
	    if (c == 'c') {
		tptr[0] = va_arg(va, int);
		tlen = 1;
		break;
	    }
	    if (c == '%') {
		/*
		 * %% = '%'.  tptr is empty
		 */
		tptr[0] = '%';
		tlen = 1;
		break;
	    }

	    switch(c) {
	    case '-':
		flags |= FMT_LEFT;
		flags &= ~FMT_RIGHT;
		break;
	    case 'l':
		flags |= FMT_LONG;
		break;
	    case '0':
		flags |= FMT_ZFILL;
		/* fall through */
	    case '1':
	    case '2':
	    case '3':
	    case '4':
	    case '5':
	    case '6':
	    case '7':
	    case '8':
	    case '9':
		/*
		 * field width
		 */
		{
		    char *ptr;
		    fwidth = strtol(ctl + i, &ptr, 10);
		    i = ptr - ctl - 1;
		}
		break;
	    default:
		fatal("MiniPfmt: unknown ctl character '%c' in %s", c, ctl);
		/* NOT REACHED */
	    }
	}

	/*
	 * Handle negative sign.  It must occur prior to
	 * any zero-fill, but be next to a right-justified
	 * number.
	 */
	if (flags & FMT_ISNEG) {
	    if ((flags & FMT_ZFILL) && tlen < fwidth) {
		--fwidth;
		callback(info, "-", 1);
		++r;
	    } else {
		*--tptr = '-';
		++tlen;
	    }
	}

	/*
	 * handle (default) right justification
	 * handle zero-fill
	 * (neither is set if buffer is left justified)
	 */
	while (tlen < fwidth) {
	    int n = (fwidth - tlen > 16) ? 16 : fwidth - tlen;

	    if (flags & FMT_ZFILL) {
		callback(info, "0000000000000000", n);
		fwidth -= n;
		r += n;
		continue;
	    } else if (flags & FMT_RIGHT) {
		callback(info, "                ", n);
		fwidth -= n;
		r += n;
		continue;
	    }
	    break;
	}

	/*
	 * output tptr
	 */
	if (tlen) {
	    callback(info, tptr, tlen);
	    r += tlen;
	}

	/*
	 * handle left justification
	 */

	while (tlen < fwidth && (flags & FMT_LEFT)) {
	    int n = (fwidth - tlen > 16) ? 16 : fwidth - tlen;
	    callback(info, "                ", n);
	    fwidth -= n;
	    r += n;
	}
	/*
	 * i points to the control terminator, so allow the for() loop to 
	 * increment it.
	 */
	b = i + 1;
    }
    if (i != b) {
	callback(info, ctl + b, i - b);
	r += i - b;
    }
    return(r);
}

#endif

/*
 *
 *	TEST CODE
 *
 */

#ifdef TEST

int
main(int ac, char **av)
{
    char buf[32];
    int r;

    if (ac == 1)
	exit(0);

    r = snprintf(buf, sizeof(buf), av[1], 203, 11, -500, "charlie");
    printf("(%d): %s\n", r, buf);
    exit(0);
}

void
fatal(const char *ctl, ...)
{
    va_list va;

    va_start(va, ctl);
    vfprintf(stderr, ctl, va);
    va_end(va);
    fprintf(stderr, "\n");
    fflush(stderr);
    exit(1);
}

#endif



syntax highlighted by Code2HTML, v. 0.9.1