util-snprintf-replacement-20090624
[openafs.git] / src / util / snprintf.c
index 5a5004a..1bbe36e 100644 (file)
@@ -1,3 +1,36 @@
+/*
+ * Copyright (c) 1997 - 2004 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
 /* snprintf.c - Formatted, length-limited print to a string */
 
 #include <afsconfig.h>
@@ -7,6 +40,7 @@ RCSID
     ("$Header$");
 
 #include <sys/types.h>
+#include <stdlib.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <ctype.h>
@@ -24,45 +58,12 @@ RCSID
 #include <sys/socket.h>
 #endif
 
-#define MAXPREC 100
-
-/* Generate an ASCII representation of an integer <val>, as follows:
- * <base> indicates the base to be used (2-36)
- * <uc> is nonzero if letter digits should be uppercase
- * <prec> is the minimum number of digits
- * The resulting number is stored in <buf>, which must be long enough
- * to receive it.  The minimum length is <prec> or ceil(log{base}(val)),
- * whichever is larger, plus room for a trailing NUL.
- */
-static void
-mkint(char *buf, afs_uintmax_t val, int base, int uc, unsigned prec)
-{
-    int len = 0, dig, i;
-
-    while (val) {
-       dig = (int) (val % base);
-       val = (val - dig) / base;
-       if (dig < 10)
-           dig = dig + '0';
-       else if (uc)
-           dig = dig + 'A' - 10;
-       else
-           dig = dig + 'a' - 10;
-       buf[len++] = dig;
-    }
-    while (len < prec)
-       buf[len++] = '0';
-    for (i = 0; i < (len + 1) / 2; i++) {
-       dig = buf[i];
-       buf[i] = buf[len - i - 1];
-       buf[len - i - 1] = dig;
-    }
-    buf[len] = 0;
-}
-
-
-/* This function is a mostly-complete implementation of snprintf,
- * with the following features:
+/* This is an enhanced version of the *printf functionality shipped 
+ * with Heimdal.  In addition to the standard Unix formatting types
+ * this version also supports Microsoft's I32 and I64 type modifiers
+ * and the OpenAFS I type which is used to generate output from 
+ * network byte order IPv4 addresses (either dotted notation or 
+ * hostname lookups.  Implementation details follow:
  *
  *   - Actually obeys the length limit, which (unfortunately) many
  *     implementations of snprintf do not.
@@ -71,6 +72,10 @@ mkint(char *buf, afs_uintmax_t val, int base, int uc, unsigned prec)
  *     (d, i, o, u, x, X), floating-point values (f, e, E, g, G),
  *     and strings and characters (c, s, %), plus a few unusual
  *     but useful ones described below.
+ *
+ *   - The Microsoft integral size modifiers I32 and I64 are 
+ *     supported.  I32 is equivalent to 'l'.
+ *     I64 is equivalent to 'll'.
  *  
  *   - Supports all the standard flags (-, 0, +, space, #).  These
  *     flags are ignored if used when they are not appropriate.
@@ -84,10 +89,8 @@ mkint(char *buf, afs_uintmax_t val, int base, int uc, unsigned prec)
  *     instead of in the format string.  There is a maximum precision
  *     of 100 digits.
  *  
- *   - At present, the 'p' specifier for printing pointers is not
- *     implemented, because it is inherently non-portable and thus
- *     can be implemented correctly only by the compiler's run-time
- *     library.
+ *   - The 'p' specifier for printing pointers is implemented using
+ *     compile time knowledge.  (AFS_64BITPOINTER_ENV)
  *
  *   - Floating-point specifier (%e, %f, %g) are implemented by
  *     calling the standard sprintf, and thus may be unsafe.
@@ -96,6 +99,10 @@ mkint(char *buf, afs_uintmax_t val, int base, int uc, unsigned prec)
  *     is specified by the user, who knows but cannot change the order
  *     of the arguments.  Such usage is inherently dangerous and
  *     insecure; thus, it is not supported.
+ *
+ *   - Passing in a format and an NULL buffer is supported.  This
+ *     will compute the size of the buffer required by the format
+ *     and the provided input parameters.
  *  
  * The custom format specifier '%I' is supported.  This specifier
  * takes as its argument an unsigned long integer containing an
@@ -130,403 +137,809 @@ mkint(char *buf, afs_uintmax_t val, int base, int uc, unsigned prec)
  *       will be padded with spaces on the left to three digits.  If
  *       both '0' and ' ' are given, the ' ' flag will be ignored.
  *     + The '#' and '+' flags have no effect.
+ *
+ * A test program exists in src/util/tests/snprintf_tests.c.
  */
-int
-afs_vsnprintf(char *p, size_t avail, const char *fmt, va_list ap)
+
+#define MAXPREC 100 
+
+enum format_flags {
+    minus_flag     =  1,
+    plus_flag      =  2,
+    space_flag     =  4,
+    alternate_flag =  8,
+    zero_flag      = 16
+};
+
+/*
+ * Common state
+ */
+
+struct snprintf_state {
+    unsigned char *str;
+    unsigned char *s;
+    unsigned char *theend;
+    size_t sz;
+    size_t max_sz;
+    void (*append_char)(struct snprintf_state *, unsigned char);
+    /* XXX - methods */
+};
+
+static int
+afs_sn_reserve (struct snprintf_state *state, size_t n)
 {
-    unsigned int width, precision, haveprec;
-    size_t len;
-    int ljust, plsign, spsign, altform, zfill;
-    int hflag, lflag, count, *countp, j;
-    char *x, *y, xbuf[MAXPREC + 21], fbuf[20];
-    struct hostent *he;
-    struct in_addr ia;
-    afs_uintmax_t UVAL;
-    afs_intmax_t SVAL;
-    afs_intmax_t *llcountp;
-    long *lcountp;
-    double FVAL;
-    short *hcountp;
-
-    count = 0;
-    avail--;
-    while (*fmt && avail) {
-       if (*fmt != '%') {
-           *p++ = *fmt++;
-           avail--;
-           count++;
-           continue;
+    return state->s + n > state->theend;
+}
+
+static void
+afs_sn_append_char (struct snprintf_state *state, unsigned char c)
+{
+    if (!afs_sn_reserve (state, 1))
+       *state->s++ = c;
+}
+
+static int
+as_reserve (struct snprintf_state *state, size_t n)
+{
+    if (state->s + n > state->theend) {
+       size_t off = state->s - state->str;
+       unsigned char *tmp;
+
+       if (state->max_sz && state->sz >= state->max_sz)
+           return 1;
+
+       state->sz = max(state->sz * 2, state->sz + n);
+       if (state->max_sz)
+           state->sz = min(state->sz, state->max_sz);
+       tmp = (unsigned char *)realloc (state->str, state->sz);
+       if (tmp == NULL)
+           return 1;
+       state->str = tmp;
+       state->s = state->str + off;
+       state->theend = state->str + state->sz - 1;
+    }
+    return 0;
+}
+
+static void
+as_append_char (struct snprintf_state *state, unsigned char c)
+{
+    if(!as_reserve (state, 1))
+       *state->s++ = c;
+}
+
+/* longest integer types */
+
+#ifdef AFS_64BIT_ENV
+typedef afs_uint64 u_longest;
+typedef afs_int64 longest;
+#else
+typedef afs_uint32 u_longest;
+typedef afs_int32 longest;
+#endif
+
+static int
+pad(struct snprintf_state *state, int width, char c)
+{
+    int len = 0;
+    while(width-- > 0){
+       (*state->append_char)(state,  c);
+       ++len;
+    }
+    return len;
+}
+
+/* return true if we should use alternatve hex form */
+static int
+use_alternative (int flags, u_longest num, unsigned base)
+{
+    return (flags & alternate_flag) && base == 16 && num != 0;
+}
+
+static int
+append_number(struct snprintf_state *state,
+             u_longest num, unsigned base, const char *rep,
+             int width, int prec, int flags, int minusp)
+{
+    int len = 0;
+    u_longest n = num;
+    char nstr[MAXPREC]; /* enough for <192 bit octal integers */
+    int nstart, nlen;
+    char signchar;
+
+    /* given precision, ignore zero flag */
+    if(prec != -1)
+       flags &= ~zero_flag;
+    else
+       prec = 1;
+
+    /* format number as string */
+    nstart = sizeof(nstr);
+    nlen = 0;
+    nstr[--nstart] = '\0';
+
+    do {
+       nstr[--nstart] = rep[n % base];
+       ++nlen;
+       n /= base;
+    } while(n);
+
+    /* zero value with zero precision should produce no digits */
+    if(prec == 0 && num == 0) {
+       nlen--;
+       nstart++;
+    }
+
+    /* figure out what char to use for sign */
+    if(minusp)
+       signchar = '-';
+    else if((flags & plus_flag))
+       signchar = '+';
+    else if((flags & space_flag))
+       signchar = ' ';
+    else
+       signchar = '\0';
+
+    if((flags & alternate_flag) && base == 8) {
+       /* if necessary, increase the precision to
+          make first digit a zero */
+
+       /* XXX C99 claims (regarding # and %o) that "if the value and
+           precision are both 0, a single 0 is printed", but there is
+           no such wording for %x. This would mean that %#.o would
+           output "0", but %#.x "". This does not make sense, and is
+           also not what other printf implementations are doing. */
+       
+       if(prec <= nlen && nstr[nstart] != '0' && nstr[nstart] != '\0')
+           prec = nlen + 1;
+    }
+
+    /* possible formats:
+       pad | sign | alt | zero | digits
+       sign | alt | zero | digits | pad   minus_flag
+       sign | alt | zero | digits zero_flag */
+
+    /* if not right justifying or padding with zeros, we need to
+       compute the length of the rest of the string, and then pad with
+       spaces */
+    if(!(flags & (minus_flag | zero_flag))) {
+       if(prec > nlen)
+           width -= prec;
+       else
+           width -= nlen;
+       
+       if(use_alternative(flags, num, base))
+           width -= 2;
+       
+       if(signchar != '\0')
+           width--;
+       
+       /* pad to width */
+       len += pad(state, width, ' ');
+    }
+    if(signchar != '\0') {
+       (*state->append_char)(state, signchar);
+       ++len;
+    }
+    if(use_alternative(flags, num, base)) {
+       (*state->append_char)(state, '0');
+       (*state->append_char)(state, rep[10] + 23); /* XXX */
+       len += 2;
+    }
+    if(flags & zero_flag) {
+       /* pad to width with zeros */
+       if(prec - nlen > width - len - nlen)
+           len += pad(state, prec - nlen, '0');
+       else
+           len += pad(state, width - len - nlen, '0');
+    } else
+       /* pad to prec with zeros */
+       len += pad(state, prec - nlen, '0');
+       
+    while(nstr[nstart] != '\0') {
+       (*state->append_char)(state, nstr[nstart++]);
+       ++len;
+    }
+       
+    if(flags & minus_flag)
+       len += pad(state, width - len, ' ');
+
+    return len;
+}
+
+/*
+ * return length
+ */
+
+static int
+append_string (struct snprintf_state *state,
+              const unsigned char *arg,
+              int width,
+              int prec,
+              int flags)
+{
+    int len = 0;
+
+    if(arg == NULL)
+       arg = (const unsigned char*)"(null)";
+
+    if(prec != -1)
+       width -= prec;
+    else
+       width -= (int)strlen((const char *)arg);
+    if(!(flags & minus_flag))
+       len += pad(state, width, ' ');
+
+    if (prec != -1) {
+       while (*arg && prec--) {
+           (*state->append_char) (state, *arg++);
+           ++len;
+       }
+    } else {
+       while (*arg) {
+           (*state->append_char) (state, *arg++);
+           ++len;
        }
+    }
+    if(flags & minus_flag)
+       len += pad(state, width, ' ');
+    return len;
+}
 
-    /** Found a format specifier **/
-       ljust = plsign = spsign = altform = zfill = 0;
-       width = precision = haveprec = 0;
-       hflag = lflag = 0;
-       fmt++;
-
-       /* parse format flags */
-       while (*fmt) {
-           switch (*fmt) {
-           case '-':
-               ljust = 1;
-               fmt++;
-               continue;       /* left justify */
-           case '+':
-               plsign = 1;
-               fmt++;
-               continue;       /* use + or - */
-           case ' ':
-               spsign = 1;
-               fmt++;
-               continue;       /* use space or - */
-           case '#':
-               altform = 1;
-               fmt++;
-               continue;       /* alternate form */
-           case '0':
-               zfill = 1;
-               fmt++;
-               continue;       /* pad with 0 */
-           default:
-               break;
+static int
+append_char(struct snprintf_state *state,
+           unsigned char arg,
+           int width,
+           int flags)
+{
+    int len = 0;
+
+    while(!(flags & minus_flag) && --width > 0) {
+       (*state->append_char) (state, ' ')    ;
+       ++len;
+    }
+    (*state->append_char) (state, arg);
+    ++len;
+    while((flags & minus_flag) && --width > 0) {
+       (*state->append_char) (state, ' ');
+       ++len;
+    }
+    return 0;
+}
+
+#define MAXPREC 100 
+static int
+append_float(struct snprintf_state *state,
+             char type,
+             double arg,
+             int width,
+             int prec,
+             int flags)
+{
+    int len = 0;
+    char fbuf[20], xbuf[MAXPREC + 21];
+
+    sprintf(fbuf, "%%%s%s.*L%c", 
+            (flags & plus_flag) ? "+" : ((flags & space_flag) ? " " : ((flags & minus_flag) ? "-" : "")),
+            (flags & alternate_flag) ? "#" : "", type);
+    if (prec == -1)
+        prec = 6;
+    if (prec > MAXPREC)
+       prec = MAXPREC;
+    sprintf(xbuf, fbuf, prec, arg);
+
+    len = append_string(state, (unsigned char *)xbuf, width, -1, 0);
+    return len;
+}
+
+static int
+append_address(struct snprintf_state *state,
+               afs_uint32 arg,
+               int width,
+               int prec,
+               int flags)
+{
+    int len = 0;
+    struct hostent * he;
+    struct in_addr ia;
+    char * x, *y;
+
+    /* IP address:
+     * value is provided as a network-order unsigned long integer
+     * precision specifies max hostname length, as for %s
+     * if precision is explicitly 0, no hostname lookup is done
+     * if 0fill specified, IPaddr fields are 0-filled to 3 digits
+     * if spsign specified, IPaddr fields are space-filled to 3 digits
+     */
+    ia.s_addr = arg;
+    if (prec == 0)
+        he = 0;
+    else
+        he = gethostbyaddr((char *)&ia, 4, AF_INET);
+    if (he) {
+        x = he->h_name;
+        len = (int)strlen(x);
+        if (prec != -1 && prec < len)
+            width = prec;
+        else 
+            width = len;
+        if (flags & alternate_flag) {
+            for (y = x; *y; y++)
+                if (isupper(*y))
+                    *y = tolower(*y);
+        } else if (flags & plus_flag) {
+            for (y = x; *y; y++)
+                if (islower(*y))
+                    *y = toupper(*y);
+        }
+        len = append_string(state, (unsigned char *)x, width, prec, 0);
+    } else {
+        char xbuf[16];
+        arg = ntohl(arg);
+        if (flags & zero_flag) {
+            x = "%03u.%03u.%03u.%03u";
+        } else if (flags & space_flag) {
+            x = "%3u.%3u.%3u.%3u";
+        } else {
+            x = "%u.%u.%u.%u";
+        }
+        /* typecast to whatever '%u' is! */
+        sprintf(xbuf, x, (unsigned int)((arg & 0xff000000) >> 24),
+                         (unsigned int)((arg & 0x00ff0000) >> 16), 
+                         (unsigned int)((arg & 0x0000ff00) >> 8),
+                         (unsigned int)(arg & 0x000000ff));
+        len = append_string(state, (unsigned char *)xbuf, 0, -1, 0);
+    }        
+
+    return len;
+}
+
+/*
+ * This can't be made into a function...
+ */
+
+#if defined(AFS_64BIT_ENV)
+#if defined(AFS_NT40_ENV)
+
+#define PARSE_INT_FORMAT(res, arg, unsig) \
+if (long_long_flag) \
+     res = (unsig __int64)va_arg(arg, unsig __int64); \
+else if (long_flag || addr_flag) \
+     res = (unsig long)va_arg(arg, unsig long); \
+else if (size_t_flag) \
+     res = (size_t)va_arg(arg, size_t); \
+else if (short_flag) \
+     res = (unsig short)va_arg(arg, unsig int); \
+else \
+     res = (unsig int)va_arg(arg, unsig int)
+
+#else /* AFS_NT40_ENV */
+
+#define PARSE_INT_FORMAT(res, arg, unsig) \
+if (long_long_flag) \
+     res = (unsig long long)va_arg(arg, unsig long long); \
+else if (long_flag || addr_flag) \
+     res = (unsig long)va_arg(arg, unsig long); \
+else if (size_t_flag) \
+     res = (size_t)va_arg(arg, size_t); \
+else if (short_flag) \
+     res = (unsig short)va_arg(arg, unsig int); \
+else \
+     res = (unsig int)va_arg(arg, unsig int)
+#endif
+
+#else
+
+#define PARSE_INT_FORMAT(res, arg, unsig) \
+if (long_flag || addr_flag) \
+     res = (afs_uint32)va_arg(arg, afs_uint32); \
+else if (size_t_flag) \
+     res = (size_t)va_arg(arg, size_t); \
+else if (short_flag) \
+     res = (unsig short)va_arg(arg, unsig int); \
+else \
+     res = (unsig int)va_arg(arg, unsig int)
+
+#endif
+
+/*
+ * zyxprintf - return length, as snprintf
+ */
+
+static int
+xyzprintf (struct snprintf_state *state, const char *char_format, va_list ap)
+{
+    const unsigned char *format = (const unsigned char *)char_format;
+    unsigned char c;
+    int len = 0;
+
+    while((c = *format++)) {
+       if (c == '%') {
+           int flags          = 0;
+           int width          = 0;
+           int prec           = -1;
+           int size_t_flag    = 0;
+           int long_long_flag = 0;
+           int long_flag      = 0;
+           int short_flag     = 0;
+            int addr_flag      = 0;
+
+           /* flags */
+           while((c = *format++)){
+               if(c == '-')
+                   flags |= minus_flag;
+               else if(c == '+')
+                   flags |= plus_flag;
+               else if(c == ' ')
+                   flags |= space_flag;
+               else if(c == '#')
+                   flags |= alternate_flag;
+               else if(c == '0')
+                   flags |= zero_flag;
+               else if(c == '\'')
+                   ; /* just ignore */
+               else
+                   break;
            }
-           break;
-       }
 
-       /* parse minimum width */
-       if (*fmt == '*') {
-           width = va_arg(ap, int);
-           fmt++;
-       } else
-           while (isdigit(*fmt)) {
-               width = (width * 10) + (*fmt - '0');
-               fmt++;
+           if((flags & space_flag) && (flags & plus_flag))
+               flags ^= space_flag;
+
+           if((flags & minus_flag) && (flags & zero_flag))
+               flags ^= zero_flag;
+
+           /* width */
+           if (isdigit(c))
+               do {
+                   width = width * 10 + c - '0';
+                   c = *format++;
+               } while(isdigit(c));
+           else if(c == '*') {
+               width = va_arg(ap, int);
+               c = *format++;
            }
 
-       /* parse precision */
-       if (*fmt == '.') {
-           fmt++;
-           haveprec = 1;
-           if (*fmt == '*') {
-               precision = va_arg(ap, int);
-               fmt++;
-           } else
-               while (isdigit(*fmt)) {
-                   precision = (precision * 10) + (*fmt - '0');
-                   fmt++;
+           /* precision */
+           if (c == '.') {
+               prec = 0;
+               c = *format++;
+               if (isdigit(c))
+                   do {
+                       prec = prec * 10 + c - '0';
+                       c = *format++;
+                   } while(isdigit(c));
+               else if (c == '*') {
+                   prec = va_arg(ap, int);
+                   c = *format++;
                }
-       }
+           }
 
-       /* parse size flags */
-       while (*fmt) {
-           switch (*fmt) {
-           case 'h':
-               hflag = 1;
-               fmt++;
-               continue;       /* short argument */
-           case 'l':
-               lflag += 1;
-               fmt++;
-               continue;       /* long argument */
-           case 'L':
-               lflag = 2;
-               fmt++;
-               continue;       /* long long argument */
-           default:
+           /* size */
+
+           if (c == 'h') {
+               short_flag = 1;
+               c = *format++;
+           } else if (c == 'z') {
+               size_t_flag = 1;
+               c = *format++;
+           } else if (c == 'l') {
+               long_flag = 1;
+               c = *format++;
+               if (c == 'l') {
+                   long_long_flag = 1;
+                   c = *format++;
+               }
+           } else if (c == 'I') {
+                /* This could be either Microsoft I{32,64} notation */
+                c = *format++;
+                if (c == '3') {
+                    long_flag = 1;
+                    c = *format++;
+                    if (c == '2') {
+                        c = *format++;
+                    }
+                } else if (c == '6') {
+                    long_flag = 1;
+                    c = *format++;
+                    if (c == '4') {
+                        long_long_flag = 1;
+                        c = *format++;
+                    }
+                } else {
+                    /* Or the OpenAFS special %I meaning network address */
+                    addr_flag = 1;
+                    --format;
+                    c = 'I';
+                }
+            } else if (c == 'p') {
+                flags |= zero_flag;
+                if (prec == -1)
+                    prec = 2 * sizeof(void *);
+                if (sizeof(void *) == sizeof(afs_uint64))
+                    long_long_flag = 1;
+                else if (sizeof(void *) == sizeof(afs_uint32))
+                    long_flag = 1;
+                else 
+                    long_flag = 1;
+            }
+
+           if(c != 'd' && c != 'i' && c != 'I')
+               flags &= ~(plus_flag | space_flag);
+
+           switch (c) {
+           case 'c' :
+               append_char(state, va_arg(ap, int), width, flags);
+               ++len;
+               break;
+           case 's' :
+               len += append_string(state,
+                                    va_arg(ap, unsigned char*),
+                                    width,
+                                    prec,
+                                    flags);
+               break;
+           case 'd' :
+           case 'i' : {
+               longest arg;
+               u_longest num;
+               int minusp = 0;
+
+               PARSE_INT_FORMAT(arg, ap, signed);
+
+               if (arg < 0) {
+                   minusp = 1;
+                   num = -arg;
+               } else
+                   num = arg;
+
+               len += append_number (state, num, 10, "0123456789",
+                                     width, prec, flags, minusp);
                break;
            }
-           break;
-       }
+           case 'u' : {
+               u_longest arg;
 
-       /* parse format specifier */
-       if (!*fmt)
-           break;
-       switch (*fmt++) {
-       case 'e':
-       case 'E':
-       case 'f':
-       case 'g':
-       case 'G':
-           FVAL = va_arg(ap, double);
-           sprintf(fbuf, "%%%s%s.*L%c", plsign ? "+" : (spsign ? " " : ""),
-                   altform ? "#" : "", fmt[-1]);
-           if (!haveprec)
-               precision = 6;
-           if (precision > MAXPREC)
-               precision = MAXPREC;
-           sprintf(xbuf, fbuf, precision, FVAL);
-           x = xbuf;
-           len = strlen(x);
-           break;
-
-       case 'i':
-       case 'd':               /* signed decimal integer */
-           if (lflag > 1)
-               SVAL = va_arg(ap, afs_intmax_t);
-           else if (lflag)
-               SVAL = va_arg(ap, long);
-           else if (hflag)
-               SVAL = va_arg(ap, int);
-           else
-               SVAL = va_arg(ap, int);
-           UVAL = (SVAL < 0) ? -SVAL : SVAL;
-
-           if (SVAL < 0)
-               xbuf[0] = '-';
-           else if (plsign)
-               xbuf[0] = '+';
-           else if (spsign)
-               xbuf[0] = ' ';
-           else
-               xbuf[0] = 0;
-
-           if (!haveprec) {
-               if (zfill && !ljust)
-                   precision = width - !!xbuf[0];
-               else
-                   precision = 1;
-               if (precision < 1 + !!xbuf[0])
-                   precision = 1 + !!xbuf[0];
-           }
-           if (precision > MAXPREC)
-               precision = MAXPREC;
-
-           mkint(xbuf + 1, UVAL, 10, 0, precision);
-           x = xbuf + !xbuf[0];
-           len = strlen(x);
-           break;
-
-
-       case 'o':               /* unsigned octal integer */
-           if (lflag > 1)
-               UVAL = va_arg(ap, afs_uintmax_t);
-           else if (lflag)
-               UVAL = va_arg(ap, unsigned long);
-           else if (hflag)
-               UVAL = va_arg(ap, unsigned int);
-           else
-               UVAL = va_arg(ap, unsigned int);
-
-           xbuf[0] = '0';
-
-           if (!haveprec) {
-               if (zfill && !ljust)
-                   precision = width;
-               else
-                   precision = 1;
+               PARSE_INT_FORMAT(arg, ap, unsigned);
+
+               len += append_number (state, arg, 10, "0123456789",
+                                     width, prec, flags, 0);
+               break;
            }
-           if (precision > MAXPREC)
-               precision = MAXPREC;
-
-           mkint(xbuf + 1, UVAL, 8, 0, precision);
-           x = xbuf + (xbuf[1] == '0' || !altform);
-           len = strlen(x);
-           break;
-
-       case 'u':               /* unsigned decimal integer */
-           if (lflag > 1)
-               UVAL = va_arg(ap, afs_uintmax_t);
-           else if (lflag)
-               UVAL = va_arg(ap, unsigned long);
-           else if (hflag)
-               UVAL = va_arg(ap, unsigned int);
-           else
-               UVAL = va_arg(ap, unsigned int);
-
-           if (!haveprec) {
-               if (zfill && !ljust)
-                   precision = width;
-               else
-                   precision = 1;
+           case 'o' : {
+               u_longest arg;
+
+               PARSE_INT_FORMAT(arg, ap, unsigned);
+
+               len += append_number (state, arg, 010, "01234567",
+                                     width, prec, flags, 0);
+               break;
            }
-           if (precision > MAXPREC)
-               precision = MAXPREC;
+           case 'x' : {
+               u_longest arg;
 
-           mkint(xbuf, UVAL, 10, 0, precision);
-           x = xbuf;
-           len = strlen(x);
-           break;
+               PARSE_INT_FORMAT(arg, ap, unsigned);
 
-       case 'p':               /* unsigned decimal integer */
-           UVAL = va_arg(ap, void *);
+               len += append_number (state, arg, 0x10, "0123456789abcdef",
+                                     width, prec, flags, 0);
+               break;
+           }
+           case 'X' :{
+               u_longest arg;
 
-           xbuf[0] = '0';
-           xbuf[1] = 'x';
+               PARSE_INT_FORMAT(arg, ap, unsigned);
 
-           if (!haveprec) {
-               if (zfill && !ljust)
-                   precision = width;
-               else
-                   precision = 1;
+               len += append_number (state, arg, 0x10, "0123456789ABCDEF",
+                                     width, prec, flags, 0);
+               break;
            }
-           if (precision > MAXPREC)
-               precision = MAXPREC;
-
-           mkint(xbuf + 2, UVAL, 16, 0, precision);
-           x = xbuf + ((altform && UVAL) ? 0 : 2);
-           len = strlen(x);
-           break;
-
-       case 'x':
-       case 'X':               /* unsigned hexadecimal integer */
-           if (lflag > 1)
-               UVAL = va_arg(ap, afs_uintmax_t);
-           else if (lflag)
-               UVAL = va_arg(ap, unsigned long);
-           else if (hflag)
-               UVAL = va_arg(ap, unsigned int);
-           else
-               UVAL = va_arg(ap, unsigned int);
-
-           xbuf[0] = '0';
-           xbuf[1] = 'x';
-
-           if (!haveprec) {
-               if (zfill && !ljust)
-                   precision = width;
-               else
-                   precision = 1;
+           case 'p' : {
+#ifdef AFS_64BITPOINTER_ENV
+               u_longest arg = (u_longest)va_arg(ap, void*);
+#else
+                u_longest arg = (unsigned long)va_arg(ap, void*);
+#endif
+               len += append_number (state, arg, 0x10, "0123456789ABCDEF",
+                                     width, prec, flags, 0);
+               break;
            }
-           if (precision > MAXPREC)
-               precision = MAXPREC;
-
-           mkint(xbuf + 2, UVAL, 16, 0, precision);
-           x = xbuf + ((altform && UVAL) ? 0 : 2);
-           len = strlen(x);
-           break;
-
-       case '%':               /* literal % */
-           xbuf[0] = '%';
-           xbuf[1] = 0;
-           x = xbuf;
-           len = 1;
-           break;
-
-       case 'c':               /* character */
-           xbuf[0] = va_arg(ap, int);
-           xbuf[1] = 0;
-           x = xbuf;
-           len = 1;
-           break;
-
-       case 's':               /* string */
-           x = va_arg(ap, char *);
-           if (!x)
-               x = "<null>";
-           len = strlen(x);
-           if (haveprec && precision < len)
-               len = precision;
-           break;
-
-       case 'I':               /* IP address:
-                                * value is provided as a network-order unsigned long integer
-                                * precision specifies max hostname length, as for %s
-                                * if precision is explicitly 0, no hostname lookup is done
-                                * if 0fill specified, IPaddr fields are 0-filled to 3 digits
-                                * if spsign specified, IPaddr fields are space-filled to 3 digits
-                                */
-           UVAL = va_arg(ap, unsigned long);
-           ia.s_addr = (unsigned long)UVAL;
-           if (haveprec && !precision)
-               he = 0;
-           else
-               he = gethostbyaddr((char *)&ia, 4, AF_INET);
-           if (he) {
-               x = he->h_name;
-               len = strlen(x);
-               if (haveprec && precision < len)
-                   len = precision;
-               if (altform) {
-                   for (y = x; *y; y++)
-                       if (isupper(*y))
-                           *y = tolower(*y);
-               } else if (plsign) {
-                   for (y = x; *y; y++)
-                       if (islower(*y))
-                           *y = toupper(*y);
-               }
-           } else {
-               UVAL = ntohl((unsigned long)UVAL);
-               if (zfill) {
-                   x = "%03u.%03u.%03u.%03u";
-               } else if (spsign) {
-                   x = "%3u.%3u.%3u.%3u";
-               } else {
-                   x = "%u.%u.%u.%u";
-               }
-               /* typecast to whatever '%u' is! */
-               sprintf(xbuf, x, (unsigned int)((UVAL & 0xff000000) >> 24),
-                       (unsigned int)((UVAL & 0x00ff0000) >> 16), 
-                       (unsigned int)((UVAL & 0x0000ff00) >> 8),
-                       (unsigned int)(UVAL & 0x000000ff));
-               x = xbuf;
-               len = strlen(xbuf);
+           case 'n' : {
+               int *arg = va_arg(ap, int*);
+               *arg = (int)(state->s - state->str);
+               break;
            }
-           break;
-
-       case 'n':               /* report count so far */
-           if (lflag > 1) {
-               llcountp = va_arg(ap, afs_intmax_t *);
-               *llcountp = (afs_intmax_t)count;
-           } else if (lflag) {
-               lcountp = va_arg(ap, long *);
-               *lcountp = (long)count;
-           } else if (hflag) {
-               hcountp = va_arg(ap, short *);
-               *hcountp = (short)count;
-           } else {
-               countp = va_arg(ap, int *);
-               *countp = count;
+            case 'I' : {
+                u_longest arg;
+
+                PARSE_INT_FORMAT(arg, ap, unsigned);
+
+                len += append_address (state, (unsigned long)arg, width, prec, flags);
+                break;
+            }
+            case 'e':
+            case 'E':
+            case 'f':
+            case 'g':
+            case 'G': {
+                double arg = (double)va_arg(ap, double);
+
+                len += append_float (state, c, arg, width, prec, flags);
+                break;
+            }
+           case '\0' :
+               --format;
+               /* FALLTHROUGH */
+           case '%' :
+               (*state->append_char)(state, c);
+               ++len;
+               break;
+           default :
+               (*state->append_char)(state, '%');
+               (*state->append_char)(state, c);
+               len += 2;
+               break;
            }
-           continue;
-
-       default:                /* unknown specifier */
-           continue;
+       } else {
+           (*state->append_char) (state, c);
+           ++len;
        }
+    }
+    return len;
+}
 
-       /* render the results */
-       if (len > avail)
-           len = avail;
-       if (!width)
-           width = len;
-       if (width > avail)
-           width = avail;
-       j = width - len;
-       if (j > 0) {
-           avail -= j;
-           count += j;
-       }
 
-       if (!ljust)
-           while (j-- > 0)
-               *p++ = ' ';
+int
+afs_vsnprintf (char *str, size_t sz, const char *format, va_list args)
+{
+    struct snprintf_state state;
+    int ret;
+    unsigned char *ustr = (unsigned char *)str;
+
+    state.max_sz = 0;
+    state.sz     = sz;
+    state.str    = ustr;
+    state.s      = ustr;
+    state.theend = ustr + sz - (sz > 0);
+    state.append_char = afs_sn_append_char;
+
+    ret = xyzprintf (&state, format, args);
+    if (state.s != NULL && sz != 0)
+       *state.s = '\0';
+    return ret;
+}
+
+int
+afs_snprintf (char *str, size_t sz, const char *format, ...)
+{
+    va_list args;
+    int ret;
+
+    va_start(args, format);
+    ret = afs_vsnprintf (str, sz, format, args);
+    va_end(args);
+
+#ifdef PARANOIA
+    {
+       int ret2;
+       unsigned char *tmp;
+
+       tmp = (unsigned char *)malloc (sz);
+       if (tmp == NULL)
+           abort ();
+
+       va_start(args, format);
+       ret2 = afs_vsprintf (tmp, format, args);
+       va_end(args);
+       if (ret != ret2 || strcmp(str, tmp))
+           abort ();
+       free (tmp);
+    }
+#endif
 
-       strncpy(p, x, len);
-       avail -= len;
-       count += len;
-       p += len;
+    return ret;
+}
 
-       if (ljust)
-           while (j-- > 0)
-               *p++ = ' ';
+int
+afs_vasnprintf (char **ret, size_t max_sz, const char *format, va_list args)
+{
+    int st;
+    struct snprintf_state state;
+
+    state.max_sz = max_sz;
+    state.sz     = 1;
+    state.str    = (unsigned char *)malloc(state.sz);
+    if (state.str == NULL) {
+       *ret = NULL;
+       return -1;
+    }
+    state.s = state.str;
+    state.theend = state.s + state.sz - 1;
+    state.append_char = as_append_char;
+
+    st = xyzprintf (&state, format, args);
+    if (st > state.sz) {
+       free (state.str);
+       *ret = NULL;
+       return -1;
+    } else {
+       char *tmp;
+
+       *state.s = '\0';
+       tmp = (char *)realloc (state.str, st+1);
+       if (tmp == NULL) {
+           free (state.str);
+           *ret = NULL;
+           return -1;
+       }
+       *ret = tmp;
+       return st;
     }
-    *p = 0;
-    return count;
 }
 
 int
-afs_snprintf(char *p, size_t avail, const char *fmt, ...)
+afs_vasprintf (char **ret, const char *format, va_list args)
 {
-    va_list ap;
-    int result;
+    return afs_vasnprintf (ret, 0, format, args);
+}
 
-    va_start(ap, fmt);
-    result = afs_vsnprintf(p, avail, fmt, ap);
-    va_end(ap);
-    return result;
+int
+afs_asprintf (char **ret, const char *format, ...)
+{
+    va_list args;
+    int val;
+
+    va_start(args, format);
+    val = afs_vasprintf (ret, format, args);
+    va_end(args);
+
+#ifdef PARANOIA
+    {
+       int ret2;
+       unsigned char *tmp;
+       tmp = (unsigned char *)malloc (val + 1);
+       if (tmp == NULL)
+           abort ();
+
+       va_start(args, format);
+       ret2 = afs_vsprintf (tmp, format, args);
+       va_end(args);
+       if (val != ret2 || strcmp(*ret, tmp))
+           abort ();
+       free (tmp);
+    }
+#endif
+
+    return val;
+}
+
+int
+afs_asnprintf (char **ret, size_t max_sz, const char *format, ...)
+{
+    va_list args;
+    int val;
+
+    va_start(args, format);
+    val = afs_vasnprintf (ret, max_sz, format, args);
+
+#ifdef PARANOIA
+    {
+       int ret2;
+       unsigned char *tmp;
+       tmp = (unsigned char *)malloc (val + 1);
+       if (tmp == NULL)
+           abort ();
+
+       ret2 = afs_vsprintf (tmp, format, args);
+       if (val != ret2 || strcmp(*ret, tmp))
+           abort ();
+       free (tmp);
+    }
+#endif
+
+    va_end(args);
+    return val;
 }
 
-#if defined(AFS_OSF20_ENV) && !defined(AFS_DUX50_ENV) || defined(AFS_AIX32_ENV) || (defined(AFS_SUN55_ENV) && !defined(AFS_SUN56_ENV)) || !defined(HAVE_VSNPRINTF)
+#if defined(AFS_OSF20_ENV) && !defined(AFS_DUX50_ENV) || defined(AFS_AIX32_ENV) || (defined(AFS_SUN55_ENV) && !defined(AFS_SUN56_ENV)) || !defined(HAVE_VSNPRINTF) || defined(TEST_SNPRINTF)
 
 #if defined(AFS_AIX51_ENV) || defined(AFS_NT40_ENV)
 int
@@ -550,7 +963,7 @@ void
 vsyslog(int priority, const char *format, va_list args)
 {
   char buf[1024];
-  vsnprintf(buf, sizeof(buf), format, args);
+  afs_vsnprintf(buf, sizeof(buf), format, args);
   syslog(priority, "%s", buf);
 }
 #endif