/* An implementation of vsnprintf() for systems that don't have it.
*
* IRC Services is copyright (c) 1996-2007 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file COPYING for
* details.
*/
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
typedef int (*_pfmt_writefunc_t)
(const char *buf, size_t len, void *arg1, void *arg2);
int my_vsnprintf(char *string, size_t size, const char *format, va_list args);
int my_snprintf(char *string, size_t size, const char *format, ...);
/*************************************************************************/
/* Basic format routine for *printf() interfaces. Takes a writing function
* and two (optional) parameters, one pointer and one integer, for that
* function which are passed on unmodified. The function should return the
* number of bytes written, and 0 (not -1!) on write failure.
*/
static int _pfmt(const char *format, va_list args,
_pfmt_writefunc_t writefunc, void *arg1, void *arg2)
{
int total = 0; /* Total bytes written */
const char *startptr;/* Beginning of non-token text in format string.
* Used for writing in bulk instead of
* character-at-a-time. */
int n; /* Bytes written in last writefunc() call */
int valid; /* Was this a valid %-token? */
int alt_form; /* "Alternate form"? (# flag) */
int zero_pad; /* Zero-pad value? */
int left_justify; /* Left-justify? (0 means right-justify) */
int always_sign; /* Always add sign value? */
int insert_blank; /* Insert blank before positive values for %d/%i? */
int width; /* Field width */
int precision; /* Precision */
int argsize; /* Size of argument: 0=normal, 1=short, 2=long,
* 3=long long */
int what; /* What are we working on? 0=flags, 1=width,
* 2=precision, 3=argsize, 4=argtype */
long intval; /* Integer value */
char *strval; /* String value */
void *ptrval; /* Pointer value */
char numbuf[64]; /* Temporary buffer for printing numbers; this MUST
* be large enough to hold the longest possible
* string (size is not checked in processing) */
char *numptr; /* Pointer to start of printed number in `numbuf' */
intval = 0;
strval = NULL;
ptrval = NULL;
startptr = format;
while (*format) {
if (*format != '%') {
format++;
continue;
}
if (startptr != format) {
/* Write out accumulated text */
n = writefunc(startptr, format-startptr, arg1, arg2);
total += n;
/* Abort on short write */
if (n != format-startptr)
break;
/* Point to this token, in case it's a bad one */
startptr = format;
}
/* Begin %-token processing */
valid = 0; /* 1 if valid, -1 if known not valid (syntax error) */
alt_form = 0;
left_justify = 0;
always_sign = 0;
insert_blank = 0;
zero_pad = 0;
width = -1;
precision = -1;
argsize = 0;
what = 0;
while (!valid && *++format) { /* Broken out of by terminal chars */
switch (*format) {
/* Flags */
case '#':
if (what != 0) {
valid = -1;
break;
}
alt_form = 1;
break;
case '-':
if (what != 0) {
valid = -1;
break;
}
left_justify = 1;
break;
case '+':
if (what != 0) {
valid = -1;
break;
}
always_sign = 1;
break;
case ' ':
if (what != 0) {
valid = -1;
break;
}
insert_blank = 1;
break;
case '\'':
/* Comma-grouping by locale; not supported */
valid = -1;
break;
case '0':
if (what == 0) {
zero_pad = 1;
break;
}
/* else fall through */
/* Field widths */
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (what == 0)
what = 1;
else if (what > 2) {
valid = -1;
break;
}
if (what == 1) {
if (width < 0)
width = 0;
width = width*10 + (*format)-'0';
} else {
if (precision < 0)
precision = 0;
precision = precision*10 + (*format)-'0';
}
break;
case '*':
if (what == 0)
what = 1;
else if (what >= 2) {
valid = -1;
break;
}
if (what == 1) {
width = va_arg(args, int);
if (width < 0) {
width = -width;
left_justify = 1;
}
} else {
precision = va_arg(args, int);
}
break;
case '.':
if (what >= 2) {
valid = -1;
break;
}
what = 2;
break;
/* Argument sizes */
case 'h':
if (what > 3) {
valid = -1;
break;
}
argsize = 1;
what = 4;
break;
case 'l':
if (what > 3) {
valid = -1;
break;
}
argsize = 2;
what = 4;
break;
case 'L':
/* Long long (64 bits); not supported */
valid = -1;
break;
/* Argument types */
case 'd':
case 'i':
case 'o':
case 'u':
case 'x':
case 'X':
if (argsize == 1)
intval = va_arg(args, int);
else if (argsize == 2)
intval = va_arg(args, long);
else
intval = va_arg(args, int);
valid = 1;
break;
case 'c':
intval = va_arg(args, int);
valid = 1;
break;
case 's':
strval = va_arg(args, char *);
valid = 1;
break;
case 'p':
ptrval = va_arg(args, void *);
valid = 1;
break;
case 'n':
*((int *)va_arg(args, int *)) = total;
valid = 1;
break;
/* All other characters--this neatly catches "%%" too */
default:
valid = -1;
break;
} /* switch (*format) */
}
if (valid != 1) {
/* Not a valid %-token; start loop over (token will get printed
* out next time through). */
continue;
}
/* Don't zero-pad if a precision was given or left-justifying */
if (precision != -1 || left_justify)
zero_pad = 0;
/* For numbers, limit precision to the size of the print buffer */
if ((*format=='d' || *format=='i' || *format=='o' || *format=='u'
|| *format=='x' || *format=='X')
&& precision > (signed) sizeof(numbuf))
{
precision = sizeof(numbuf);
}
switch (*format++) { /* Do something with this token */
case 'p':
/* Print the NULL value specially */
if (ptrval == NULL) {
total += writefunc("(null)", 6, arg1, arg2);
break;
}
/* For all other values, pretend it's really %#.8x */
alt_form = 1;
zero_pad = 0;
precision = 8;
intval = (long) ptrval;
/* Fall through */
case 'x':
case 'X': {
static const char x_chars[] = "0123456789abcdef0x";
static const char X_chars[] = "0123456789ABCDEF0X";
const char *chars = (format[-1]=='X') ? X_chars : x_chars;
const char *padstr = zero_pad ? "0" : " ";
unsigned long uintval;
int len;
uintval = (unsigned long) intval;
if (alt_form && uintval != 0) {
n = writefunc(chars+16, 2, arg1, arg2);
total += n;
if (n != 2)
break;
width -= 2;
}
if (precision < 1)
precision = 1;
numptr = numbuf + sizeof(numbuf);
for (len = 0; len < precision || uintval != 0; len++) {
*--numptr = chars[uintval%16];
uintval /= 16;
}
if (left_justify) {
n = writefunc(numptr, len, arg1, arg2);
total += n;
if (n != len)
break;
}
while (len < width) {
if (1 != writefunc(padstr, 1, arg1, arg2))
break;
total++;
width--;
}
if (!left_justify)
total += writefunc(numptr, len, arg1, arg2);
break;
} /* case 'x', 'X' */
case 'o': {
const char *padstr = zero_pad ? "0" : " ";
unsigned long uintval;
int len;
uintval = (unsigned long) intval;
if (precision < 1)
precision = 1;
numptr = numbuf + sizeof(numbuf);
for (len = 0; len < precision || uintval != 0; len++) {
*--numptr = '0' + uintval%8;
uintval /= 8;
}
if (alt_form && *numptr != '0') {
*--numptr = '0';
len++;
}
if (left_justify) {
n = writefunc(numptr, len, arg1, arg2);
total += n;
if (n != len)
break;
}
while (len < width) {
if (1 != writefunc(padstr, 1, arg1, arg2))
break;
total++;
width--;
}
if (!left_justify)
total += writefunc(numptr, len, arg1, arg2);
break;
} /* case 'o' */
case 'u': {
const char *padstr = zero_pad ? "0" : " ";
unsigned long uintval;
int len;
uintval = (unsigned long) intval;
if (precision < 1)
precision = 1;
numptr = numbuf + sizeof(numbuf);
for (len = 0; len < precision || uintval != 0; len++) {
*--numptr = '0' + uintval%10;
uintval /= 10;
}
if (left_justify) {
n = writefunc(numptr, len, arg1, arg2);
total += n;
if (n != len)
break;
}
while (len < width) {
if (1 != writefunc(padstr, 1, arg1, arg2))
break;
total++;
width--;
}
if (!left_justify)
total += writefunc(numptr, len, arg1, arg2);
break;
} /* case 'u' */
case 'd':
case 'i': {
const char *padstr = zero_pad ? "0" : " ";
int len;
char sign_char;
numptr = numbuf + sizeof(numbuf);
len = 0;
sign_char = 0;
if (intval < 0) {
sign_char = '-';
intval = -intval;
if (intval < 0) { /* true for 0x800...0 */
*numptr-- = '0' - intval%10;
len++;
intval /= 10;
intval = -intval;
}
}
if (precision < 1)
precision = 1;
for (; len < precision || intval != 0; len++) {
*--numptr = '0' + intval%10;
intval /= 10;
}
if (!sign_char) {
if (always_sign)
sign_char = '+';
else if (insert_blank)
sign_char = ' ';
}
if (sign_char) {
if (zero_pad) {
if (1 != writefunc(&sign_char, 1, arg1, arg2))
break;
total++;
width--;
} else {
*--numptr = sign_char;
len = 0;
}
}
if (left_justify) {
n = writefunc(numptr, len, arg1, arg2);
total += n;
if (n != len)
break;
}
while (len < width) {
if (1 != writefunc(padstr, 1, arg1, arg2))
break;
total++;
width--;
}
if (!left_justify)
total += writefunc(numptr, len, arg1, arg2);
break;
} /* case 'd', 'i' */
case 'c': {
const char *padstr = zero_pad ? "0" : " ";
unsigned char c = (unsigned char) intval;
if (left_justify) {
if (1 != writefunc(&c, 1, arg1, arg2))
break;
total++;
}
while (width > 1) {
if (1 != writefunc(padstr, 1, arg1, arg2))
break;
total++;
width--;
}
if (!left_justify)
total += writefunc(&c, 1, arg1, arg2);
break;
} /* case 'c' */
case 's': {
const char *padstr = zero_pad ? "0" : " ";
int len;
/* Catch null strings */
if (strval == NULL) {
total += writefunc("(null)", 6, arg1, arg2);
break;
}
len = strlen(strval);
if (precision < 0 || precision > len)
precision = len;
if (left_justify && precision > 0) {
n = writefunc(strval, precision, arg1, arg2);
total += n;
if (n != precision)
break;
}
while (width > precision) {
if (1 != writefunc(padstr, 1, arg1, arg2))
break;
total++;
width--;
}
if (!left_justify && precision > 0)
total += writefunc(strval, precision, arg1, arg2);
break;
} /* case 's' */
} /* switch (*format++) */
startptr = format; /* Start again after this %-token */
} /* while (*format) */
/* Write anything left over. */
if (startptr != format)
total += writefunc(startptr, format-startptr, arg1, arg2);
/* Return total bytes written. */
return total;
}
/*************************************************************************/
static int writefunc(const char *buf, size_t len, char **string, size_t *size)
{
if (*size <= 0)
return 0;
if (len > (*size)-1)
len = (*size)-1;
if (len == 0)
return 0;
memcpy(*string, buf, len);
(*string) += len;
(*string)[0] = 0;
return len;
}
int my_vsnprintf(char *string, size_t size, const char *format, va_list args)
{
int ret;
if (size <= 0)
return 0;
ret = _pfmt(format, args, (_pfmt_writefunc_t) writefunc, &string, &size);
return ret;
}
int my_snprintf(char *string, size_t size, const char *format, ...)
{
va_list args;
int ret;
va_start(args, format);
ret = my_vsnprintf(string, size, format, args);
va_end(args);
return ret;
}
/*************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1