/*
**  OSSP cfg - Configuration Parsing
**  Copyright (c) 2002-2003 Ralf S. Engelschall <rse@engelschall.com>
**  Copyright (c) 2002-2003 The OSSP Project (http://www.ossp.org/)
**
**  This file is part of OSSP cfg, a configuration parsing
**  library which can be found at http://www.ossp.org/pkg/lib/cfg/.
**
**  Permission to use, copy, modify, and distribute this software for
**  any purpose with or without fee is hereby granted, provided that
**  the above copyright notice and this permission notice appear in all
**  copies.
**
**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 AUTHORS AND COPYRIGHT HOLDERS AND THEIR
**  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.
**
**  cfg_fmt.c: printf(3)-style formatting
*/

/*
 * This is a generic printf-style formatting code which is based on
 * Apache's ap_snprintf which it turn is based on and used with the
 * permission of, the SIO stdio-replacement strx_* functions by Panos
 * Tsirigotis <panos@alumni.cs.colorado.edu> for xinetd. The IEEE
 * floating point formatting routines are derived from an anchient
 * FreeBSD version which took it from GNU libc-4.6.27 and modified it
 * to be thread safe. The whole code was finally cleaned up, stripped
 * and extended by Ralf S. Engelschall for use inside the Str library.
 * Especially any Apache and network specific kludges were removed again
 * and instead the formatting engine now can be extended by the caller
 * on-the-fly. It was then finally adjusted to be stand-alone for use
 * inside OSSP.
 */

/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2002 The Apache Software Foundation.  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. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR
 * ITS 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.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

#include "config.h"
#include "cfg_fmt.h"

/* types which are locally use */
typedef long                 long_int;
typedef unsigned long      u_long_int;
#if defined(SIZEOF_LONG_LONG) && (SIZEOF_LONG_LONG > 0)
typedef long long            quad_int;
typedef unsigned long long u_quad_int;
#else
typedef long                 quad_int;
typedef unsigned long      u_quad_int;
#endif

/* a few handy defines */
#ifndef NUL
#define NUL '\0'
#endif
#ifndef NULL
#define NULL ((void *)0)
#endif
#ifndef FALSE
#define FALSE (0)
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif
#define S_NULL          "(NULL)"
#define S_NULL_LEN      6
#define FLOAT_DIGITS    6
#define EXPONENT_LENGTH 10

/* NUM_BUF_SIZE is the size of the buffer used for arithmetic
   conversions. This is a magic number; do NOT decrease it! */
#define NUM_BUF_SIZE    512
#define NDIG            80

/* compatibility */
#if !defined(HAVE_ISNAN)
#define isnan(d) (0)
#endif
#if !defined(HAVE_ISINF)
#define isinf(d) (0)
#endif

/* explicit support for unsigned char based ctype stuff */
#define cfg_fmt_isalpha(c)  (isalpha(((unsigned char)(c))))
#define cfg_fmt_isdigit(c)  (isdigit(((unsigned char)(c))))
#define cfg_fmt_isxdigit(c) (isxdigit(((unsigned char)(c))))
#define cfg_fmt_islower(c)  (islower(((unsigned char)(c))))
#define cfg_fmt_tolower(c)  (tolower((unsigned char)(c)))

/*
 * Convert decimal number to its string representation. The number of
 * digits is specified by ndigit decpt is set to the position of the
 * decimal point sign is set to 0 for positive, 1 for negative. buf must
 * have at least NDIG bytes.
 */

#define cfg_fmt_ecvt(arg,ndigits,decpt,sign,buf) \
        cfg_fmt_cvt((arg), (ndigits), (decpt), (sign), 1, (buf))

#define cfg_fmt_fcvt(arg,ndigits,decpt,sign,buf) \
        cfg_fmt_cvt((arg), (ndigits), (decpt), (sign), 0, (buf))

/* inlined modf(3) to avoid dependency to external libm on systems
   (like Tru64, QNX, etc) where modf(3) is not part of libc.
   We use trnc to avoid conflicts with global definitions of trunc and truncate */
static double
cfg_fmt_modf(
    double arg,
    double *iptr)
{
    double fraction;
    double integral;
    long trnc;

    trnc = (long)arg;
    integral = (double)trnc;
    fraction = arg - integral;
    if (iptr != NULL)
        *iptr = integral;
    return fraction;
}

static char *
cfg_fmt_cvt(
    double arg,
    int ndigits,
    int *decpt,
    int *sign,
    int eflag,
    char *buf)
{
    register int r2;
    double fi, fj;
    register char *p, *p1;

    if (ndigits >= NDIG - 1)
        ndigits = NDIG - 2;
    r2 = 0;
    *sign = FALSE;
    p = &buf[0];
    if (arg < 0) {
        *sign = TRUE;
        arg = -arg;
    }
    arg = cfg_fmt_modf(arg, &fi);
    p1 = &buf[NDIG];

    /* Do integer part */
    if (fi != 0) {
        p1 = &buf[NDIG];
        while (fi != 0 && p1 > &buf[0]) {
            fj = cfg_fmt_modf(fi / 10, &fi);
            *--p1 = (int)((fj + .03) * 10) + '0';
            r2++;
        }
        while (p1 < &buf[NDIG])
            *p++ = *p1++;
    }
    else if (arg > 0) {
        while ((fj = arg * 10) < 1) {
            arg = fj;
            r2--;
        }
    }
    p1 = &buf[ndigits];
    if (eflag == 0)
        p1 += r2;
    *decpt = r2;
    if (p1 < &buf[0]) {
        buf[0] = NUL;
        return (buf);
    }
    while (p <= p1 && p < &buf[NDIG]) {
        arg *= 10;
        arg = cfg_fmt_modf(arg, &fj);
        *p++ = (int) fj + '0';
    }
    if (p1 >= &buf[NDIG]) {
        buf[NDIG - 1] = NUL;
        return (buf);
    }
    p = p1;
    *p1 += 5;
    while (*p1 > '9') {
        *p1 = '0';
        if (p1 > buf)
            ++ * --p1;
        else {
            *p1 = '1';
            (*decpt)++;
            if (eflag == 0) {
                if (p > buf)
                    *p = '0';
                p++;
            }
        }
    }
    *p = NUL;
    return buf;
}

static char *
cfg_fmt_gcvt(
    double number,
    int ndigit,
    char *buf,
    int altform)
{
    int sign;
    int decpt;
    register char *p1, *p2;
    register int i;
    char buf1[NDIG];

    p1 = cfg_fmt_ecvt(number, ndigit, &decpt, &sign, buf1);
    p2 = buf;
    if (sign)
        *p2++ = '-';
    for (i = ndigit - 1; i > 0 && p1[i] == '0'; i--)
        ndigit--;
    if ((decpt >= 0 && decpt - ndigit > 4)
        || (decpt < 0 && decpt < -3)) { /* use E-style */
        decpt--;
        *p2++ = *p1++;
        *p2++ = '.';
        for (i = 1; i < ndigit; i++)
            *p2++ = *p1++;
        *p2++ = 'e';
        if (decpt < 0) {
            decpt = -decpt;
            *p2++ = '-';
        }
        else
            *p2++ = '+';
        if (decpt / 100 > 0)
            *p2++ = decpt / 100 + '0';
        if (decpt / 10 > 0)
            *p2++ = (decpt % 100) / 10 + '0';
        *p2++ = decpt % 10 + '0';
    }
    else {
        if (decpt <= 0) {
            if (*p1 != '0')
                *p2++ = '.';
            while (decpt < 0) {
                decpt++;
                *p2++ = '0';
            }
        }
        for (i = 1; i <= ndigit; i++) {
            *p2++ = *p1++;
            if (i == decpt)
                *p2++ = '.';
        }
        if (ndigit < decpt) {
            while (ndigit++ < decpt)
                *p2++ = '0';
            *p2++ = '.';
        }
    }
    if (p2[-1] == '.' && !altform)
        p2--;
    *p2 = NUL;
    return buf;
}

/*
 * The INS_CHAR macro inserts a character in the buffer and flushes the
 * buffer if necessary. It uses the char pointers sp and bep: sp points
 * to the next available character in the buffer, bep points to the
 * end-of-buffer+1. While using this macro, note that the nextb pointer
 * is NOT updated. NOTE: Evaluation of the c argument should not have
 * any side-effects
 */
#define INS_CHAR(c, sp, bep, cc) {  \
    if (sp >= bep) {                \
        vbuff->curpos = sp;         \
        if (vbuff->flush(vbuff))    \
            return -1;              \
        sp = vbuff->curpos;         \
        bep = vbuff->endpos;        \
    }                               \
    *sp++ = (c);                    \
    cc++;                           \
}

/*
 * Convert a string to decimal value
 */
#define NUM(c) ((c) - '0')
#define STR_TO_DEC(str, num) {    \
    num = NUM(*str++);            \
    while (cfg_fmt_isdigit(*(str))) { \
        num *= 10 ;               \
        num += NUM(*str++) ;      \
    }                             \
}

/*
 * This macro does zero padding so that the precision requirement is
 * satisfied. The padding is done by adding '0's to the left of the
 * string that is going to be printed.
 */
#define FIX_PRECISION(adjust, precision, s, s_len)  \
    if (adjust) {                                   \
        while (s_len < precision) {                 \
            *--s = '0';                             \
            s_len++;                                \
        }                                           \
    }

/*
 * This macro does padding.
 * The padding is done by printing the character ch.
 */
#define PAD(width, len, ch) \
    do {                            \
        INS_CHAR(ch, sp, bep, cc);  \
        width--;                    \
    } while (width > len)

/*
 * Prefix the character ch to the string str
 * Increase length. Set the has_prefix flag.
 */
#define PREFIX(str, length, ch) \
    *--str = ch;                \
    length++;                   \
    has_prefix = TRUE

/*
 * Convert num to its decimal format.
 * Return value:
 *   - a pointer to a string containing the number (no sign)
 *   - len contains the length of the string
 *   - is_negative is set to TRUE or FALSE depending on the sign
 *     of the number (always set to FALSE if is_unsigned is TRUE)
 * The caller provides a buffer for the string: that is the buf_end argument
 * which is a pointer to the END of the buffer + 1 (i.e. if the buffer
 * is declared as buf[ 100 ], buf_end should be &buf[ 100 ])
 * Note: we have 2 versions. One is used when we need to use quads
 * (conv_10_quad), the other when we don't (conv_10). We're assuming the
 * latter is faster.
 */
static char *
conv_10(
    register long_int num,
    register int is_unsigned,
    register int *is_negative,
    char *buf_end,
    register size_t *len)
{
    register char *p = buf_end;
    register u_long_int magnitude;

    if (is_unsigned) {
        magnitude = (u_long_int)num;
        *is_negative = FALSE;
    }
    else {
        *is_negative = (num < 0);
        /* On a 2's complement machine, negating the most negative integer
           results in a number that cannot be represented as a signed integer.
           Here is what we do to obtain the number's magnitude:
                a. add 1 to the number
                b. negate it (becomes positive)
                c. convert it to unsigned
                d. add 1 */
        if (*is_negative) {
            long_int t = num + 1;

            magnitude = ((u_long_int) - t) + 1;
        }
        else
            magnitude = (u_long_int)num;
    }
    /* We use a do-while loop so that we write at least 1 digit */
    do {
        register u_long_int new_magnitude = magnitude / 10;
        *--p = (char) (magnitude - new_magnitude * 10 + '0');
        magnitude = new_magnitude;
    } while (magnitude);
    *len = (int)(buf_end - p);
    return p;
}

static char *
conv_10_quad(
    quad_int num,
    register int is_unsigned,
    register int *is_negative,
    char *buf_end,
    register size_t *len)
{
    register char *p = buf_end;
    u_quad_int magnitude;

    if (is_unsigned) {
        magnitude = (u_quad_int)num;
        *is_negative = FALSE;
    }
    else {
        *is_negative = (num < 0);
        /* On a 2's complement machine, negating the most negative integer
           result in a number that cannot be represented as a signed integer.
           Here is what we do to obtain the number's magnitude:
                a. add 1 to the number
                b. negate it (becomes positive)
                c. convert it to unsigned
                d. add 1 */
        if (*is_negative) {
            quad_int t = num + 1;
            magnitude = ((u_quad_int) - t) + 1;
        }
        else
            magnitude = (u_quad_int)num;
    }
    /* We use a do-while loop so that we write at least 1 digit */
    do {
        u_quad_int new_magnitude = magnitude / 10;
        *--p = (char)(magnitude - new_magnitude * 10 + '0');
        magnitude = new_magnitude;
    } while (magnitude);
    *len = (int)(buf_end - p);
    return p;
}

/*
 * Convert a floating point number to a string formats 'f', 'e' or 'E'.
 * The result is placed in buf, and len denotes the length of the string
 * The sign is returned in the is_negative argument (and is not placed
 * in buf).
 */
static char *
conv_fp(
    register char format,
    register double num,
    int add_dp,
    int precision,
    int *is_negative,
    char *buf,
    size_t *len)
{
    register char *s = buf;
    register char *p;
    int decimal_point;
    char buf1[NDIG];

    if (format == 'f')
        p = cfg_fmt_fcvt(num, precision, &decimal_point, is_negative, buf1);
    else  /* either e or E format */
        p = cfg_fmt_ecvt(num, precision + 1, &decimal_point, is_negative, buf1);

    /* Check for Infinity and NaN */
    if (cfg_fmt_isalpha(*p)) {
        *len = strlen(strcpy(buf, p));
        *is_negative = FALSE;
        return (buf);
    }

    if (format == 'f') {
        if (decimal_point <= 0) {
            *s++ = '0';
            if (precision > 0) {
                *s++ = '.';
                while (decimal_point++ < 0)
                    *s++ = '0';
            }
            else if (add_dp)
                *s++ = '.';
        }
        else {
            while (decimal_point-- > 0)
                *s++ = *p++;
            if (precision > 0 || add_dp)
                *s++ = '.';
        }
    }
    else {
        *s++ = *p++;
        if (precision > 0 || add_dp)
            *s++ = '.';
    }

    /* copy the rest of p, the NUL is NOT copied */
    while (*p)
        *s++ = *p++;

    if (format != 'f') {
        char temp[EXPONENT_LENGTH];     /* for exponent conversion */
        size_t t_len;
        int exponent_is_negative;

        *s++ = format;          /* either e or E */
        decimal_point--;
        if (decimal_point != 0) {
            p = conv_10((long_int) decimal_point, FALSE, &exponent_is_negative,
                        &temp[EXPONENT_LENGTH], &t_len);
            *s++ = exponent_is_negative ? '-' : '+';
            /* Make sure the exponent has at least 2 digits */
            if (t_len == 1)
                *s++ = '0';
            while (t_len--)
                *s++ = *p++;
        }
        else {
            *s++ = '+';
            *s++ = '0';
            *s++ = '0';
        }
    }

    *len = (int)(s - buf);
    return buf;
}

/*
 * Convert num to a base X number where X is a power of 2. nbits determines X.
 * For example, if nbits is 3, we do base 8 conversion
 * Return value:
 *      a pointer to a string containing the number
 * The caller provides a buffer for the string: that is the buf_end
 * argument which is a pointer to the END of the buffer + 1 (i.e. if the
 * buffer is declared as buf[100], buf_end should be &buf[100]) As with
 * conv_10, we have a faster version which is used when the number isn't
 * quad size.
 */

static const char low_digits[]   = "0123456789abcdef";
static const char upper_digits[] = "0123456789ABCDEF";

static char *
conv_p2(
    register u_long_int num,
    register int nbits,
    char format,
    char *buf_end,
    register size_t *len)
{
    register int mask = (1 << nbits) - 1;
    register char *p = buf_end;
    register const char *digits = (format == 'X') ? upper_digits : low_digits;

    do {
        *--p = digits[num & mask];
        num >>= nbits;
    } while (num);
    *len = (int)(buf_end - p);
    return p;
}

static char *
conv_p2_quad(
    u_quad_int num,
    register int nbits,
    char format,
    char *buf_end,
    register size_t *len)
{
    register int mask = (1 << nbits) - 1;
    register char *p = buf_end;
    register const char *digits = (format == 'X') ? upper_digits : low_digits;

    do {
        *--p = digits[num & mask];
        num >>= nbits;
    } while (num);
    *len = (size_t)(buf_end - p);
    return p;
}

/*
 * cfg_fmt_format(), the generic printf-style formatting routine
 * and heart of this piece of source.
 */
int
cfg_fmt_format(
    cfg_fmt_format_t *vbuff,
    const char *fmt,
    va_list ap)
{
    register char *sp;
    register char *bep;
    register int cc = 0;
    register unsigned int i;

    char *s = NULL;
    char *q;
    size_t s_len;

    register int min_width = 0;
    int precision = 0;
    enum { LEFT, RIGHT } adjust;
    char pad_char;
    char prefix_char;

    double fp_num;
    quad_int i_quad = (quad_int)0;
    u_quad_int ui_quad;
    long_int i_num = (long_int)0;
    u_long_int ui_num;

    char num_buf[NUM_BUF_SIZE];
    char char_buf[2]; /* for printing %% and %<unknown> */

    enum var_type_enum { IS_QUAD, IS_LONG, IS_SHORT, IS_INT };
    enum var_type_enum var_type = IS_INT;

    int alternate_form;
    int print_sign;
    int print_blank;
    int adjust_precision;
    int adjust_width;
    int is_negative;

    char extinfo[20];

    sp = vbuff->curpos;
    bep = vbuff->endpos;

    while (*fmt != NUL) {
        if (*fmt != '%') {
            INS_CHAR(*fmt, sp, bep, cc);
        }
        else {
            /*
             * Default variable settings
             */
            adjust = RIGHT;
            alternate_form = print_sign = print_blank = FALSE;
            pad_char = ' ';
            prefix_char = NUL;
            extinfo[0] = NUL;

            fmt++;

            /*
             * Try to avoid checking for flags, width or precision
             */
            if (!cfg_fmt_islower(*fmt)) {
                /*
                 * Recognize flags: -, #, BLANK, +
                 */
                for (;; fmt++) {
                    if (*fmt == '{') {
                        i = 0;
                        for (fmt++; *fmt != '}' && *fmt != NUL; fmt++) {
                            if (i < sizeof(extinfo)-1)
                                extinfo[i++] = *fmt;
                        }
                        extinfo[i] = NUL;
                    }
                    else if (*fmt == '-')
                        adjust = LEFT;
                    else if (*fmt == '+')
                        print_sign = TRUE;
                    else if (*fmt == '#')
                        alternate_form = TRUE;
                    else if (*fmt == ' ')
                        print_blank = TRUE;
                    else if (*fmt == '0')
                        pad_char = '0';
                    else
                        break;
                }

                /*
                 * Check if a width was specified
                 */
                if (cfg_fmt_isdigit(*fmt)) {
                    STR_TO_DEC(fmt, min_width);
                    adjust_width = TRUE;
                }
                else if (*fmt == '*') {
                    min_width = va_arg(ap, int);
                    fmt++;
                    adjust_width = TRUE;
                    if (min_width < 0) {
                        adjust = LEFT;
                        min_width = -min_width;
                    }
                }
                else
                    adjust_width = FALSE;

                /*
                 * Check if a precision was specified
                 *
                 * XXX: an unreasonable amount of precision may be specified
                 * resulting in overflow of num_buf. Currently we
                 * ignore this possibility.
                 */
                if (*fmt == '.') {
                    adjust_precision = TRUE;
                    fmt++;
                    if (cfg_fmt_isdigit(*fmt)) {
                        STR_TO_DEC(fmt, precision);
                    }
                    else if (*fmt == '*') {
                        precision = va_arg(ap, int);
                        fmt++;
                        if (precision < 0)
                            precision = 0;
                    }
                    else
                        precision = 0;
                }
                else
                    adjust_precision = FALSE;
            }
            else
                adjust_precision = adjust_width = FALSE;

            /*
             * Modifier check
             */
            if (*fmt == 'q') {
                var_type = IS_QUAD;
                fmt++;
            }
            else if (*fmt == 'l') {
                var_type = IS_LONG;
                fmt++;
            }
            else if (*fmt == 'h') {
                var_type = IS_SHORT;
                fmt++;
            }
            else {
                var_type = IS_INT;
            }

            /*
             * Argument extraction and printing.
             * First we determine the argument type.
             * Then, we convert the argument to a string.
             * On exit from the switch, s points to the string that
             * must be printed, s_len has the length of the string
             * The precision requirements, if any, are reflected in s_len.
             *
             * NOTE: pad_char may be set to '0' because of the 0 flag.
             *   It is reset to ' ' by non-numeric formats
             */
            switch (*fmt) {

                /* Unsigned Decimal Integer */
                case 'u':
                    if (var_type == IS_QUAD) {
                        i_quad = va_arg(ap, u_quad_int);
                        s = conv_10_quad(i_quad, 1, &is_negative,
                                         &num_buf[NUM_BUF_SIZE], &s_len);
                    }
                    else {
                        if (var_type == IS_LONG)
                            i_num = (long_int)va_arg(ap, u_long_int);
                        else if (var_type == IS_SHORT)
                            i_num = (long_int)(unsigned short)va_arg(ap, unsigned int);
                        else
                            i_num = (long_int)va_arg(ap, unsigned int);
                        s = conv_10(i_num, 1, &is_negative,
                                    &num_buf[NUM_BUF_SIZE], &s_len);
                    }
                    FIX_PRECISION(adjust_precision, precision, s, s_len);
                    break;

                /* Signed Decimal Integer */
                case 'd':
                case 'i':
                    if (var_type == IS_QUAD) {
                        i_quad = va_arg(ap, quad_int);
                        s = conv_10_quad(i_quad, 0, &is_negative,
                                         &num_buf[NUM_BUF_SIZE], &s_len);
                    }
                    else {
                        if (var_type == IS_LONG)
                            i_num = (long_int)va_arg(ap, long_int);
                        else if (var_type == IS_SHORT)
                            i_num = (long_int)(short)va_arg(ap, int);
                        else
                            i_num = (long_int)va_arg(ap, int);
                        s = conv_10(i_num, 0, &is_negative,
                                    &num_buf[NUM_BUF_SIZE], &s_len);
                    }
                    FIX_PRECISION(adjust_precision, precision, s, s_len);

                    if (is_negative)
                        prefix_char = '-';
                    else if (print_sign)
                        prefix_char = '+';
                    else if (print_blank)
                        prefix_char = ' ';
                    break;

                /* Unsigned Octal Integer */
                case 'o':
                    if (var_type == IS_QUAD) {
                        ui_quad = va_arg(ap, u_quad_int);
                        s = conv_p2_quad(ui_quad, 3, *fmt,
                                         &num_buf[NUM_BUF_SIZE], &s_len);
                    }
                    else {
                        if (var_type == IS_LONG)
                            ui_num = (u_long_int) va_arg(ap, u_long_int);
                        else if (var_type == IS_SHORT)
                            ui_num = (u_long_int)(unsigned short)va_arg(ap, unsigned int);
                        else
                            ui_num = (u_long_int)va_arg(ap, unsigned int);
                        s = conv_p2(ui_num, 3, *fmt, &num_buf[NUM_BUF_SIZE], &s_len);
                    }
                    FIX_PRECISION(adjust_precision, precision, s, s_len);
                    if (alternate_form && *s != '0') {
                        *--s = '0';
                        s_len++;
                    }
                    break;

                /* Unsigned Hexadecimal Integer */
                case 'x':
                case 'X':
                    if (var_type == IS_QUAD) {
                        ui_quad = va_arg(ap, u_quad_int);
                        s = conv_p2_quad(ui_quad, 4, *fmt,
                                         &num_buf[NUM_BUF_SIZE], &s_len);
                    }
                    else {
                        if (var_type == IS_LONG)
                            ui_num = (u_long_int)va_arg(ap, u_long_int);
                        else if (var_type == IS_SHORT)
                            ui_num = (u_long_int)(unsigned short)va_arg(ap, unsigned int);
                        else
                            ui_num = (u_long_int)va_arg(ap, unsigned int);
                        s = conv_p2(ui_num, 4, *fmt, &num_buf[NUM_BUF_SIZE], &s_len);
                    }
                    FIX_PRECISION(adjust_precision, precision, s, s_len);
                    if (alternate_form && i_num != 0) {
                        *--s = *fmt; /* 'x' or 'X' */
                        *--s = '0';
                        s_len += 2;
                    }
                    break;

                /* String */
                case 's':
                    s = va_arg(ap, char *);
                    if (s != NULL) {
                        s_len = strlen(s);
                        if (adjust_precision && precision < s_len)
                            s_len = precision;
                    }
                    else {
                        s = S_NULL;
                        s_len = S_NULL_LEN;
                    }
                    pad_char = ' ';
                    break;

                /* Double Floating Point (style 1) */
                case 'f':
                case 'e':
                case 'E':
                    fp_num = va_arg(ap, double);
                    if (isnan(fp_num)) {
                        s = "NaN";
                        s_len = 3;
                    }
                    else if (isinf(fp_num)) {
                        s = "Inf";
                        s_len = 3;
                    }
                    else {
                        /* use &num_buf[1], so that we have room for the sign */
                        s = conv_fp(*fmt, fp_num, alternate_form,
                                    (adjust_precision == FALSE) ? FLOAT_DIGITS : precision,
                                    &is_negative, &num_buf[1], &s_len);
                        if (is_negative)
                            prefix_char = '-';
                        else if (print_sign)
                            prefix_char = '+';
                        else if (print_blank)
                            prefix_char = ' ';
                    }
                    break;

                /* Double Floating Point (style 2) */
                case 'g':
                case 'G':
                    fp_num = va_arg(ap, double);
                    if (isnan(fp_num)) {
                        s = "NaN";
                        s_len = 3;
                    }
                    else if (isinf(fp_num)) {
                        s = "Inf";
                        s_len = 3;
                    }
                    else {
                        if (adjust_precision == FALSE)
                            precision = FLOAT_DIGITS;
                        else if (precision == 0)
                            precision = 1;
                        /* use &num_buf[1], so that we have room for the sign */
                        s = cfg_fmt_gcvt(fp_num, precision, &num_buf[1], alternate_form);
                        if (*s == '-')
                            prefix_char = *s++;
                        else if (print_sign)
                            prefix_char = '+';
                        else if (print_blank)
                            prefix_char = ' ';
                        s_len = strlen(s);
                        if (alternate_form && (q = strchr(s, '.')) == NULL) {
                            s[s_len++] = '.';
                            s[s_len] = NUL; /* delimit for following strchr() */
                        }
                        if (*fmt == 'G' && (q = strchr(s, 'e')) != NULL)
                            *q = 'E';
                    }
                    break;

                /* Single Character */
                case 'c':
                    char_buf[0] = (char) (va_arg(ap, int));
                    s = &char_buf[0];
                    s_len = 1;
                    pad_char = ' ';
                    break;

                /* The '%' Character */
                case '%':
                    char_buf[0] = '%';
                    s = &char_buf[0];
                    s_len = 1;
                    pad_char = ' ';
                    break;

                /* Special: Number of already written characters */
                case 'n':
                    if (var_type == IS_QUAD)
                        *(va_arg(ap, quad_int *)) = cc;
                    else if (var_type == IS_LONG)
                        *(va_arg(ap, long *)) = cc;
                    else if (var_type == IS_SHORT)
                        *(va_arg(ap, short *)) = cc;
                    else
                        *(va_arg(ap, int *)) = cc;
                    break;

                /*
                 * Pointer argument type.
                 */
                case 'p':
#if defined(SIZEOF_LONG_LONG) && (SIZEOF_LONG_LONG == SIZEOF_VOID_P)
                    ui_quad = (u_quad_int) va_arg(ap, void *);
                    s = conv_p2_quad(ui_quad, 4, 'x', &num_buf[NUM_BUF_SIZE], &s_len);
#else
                    ui_num = (u_long_int) va_arg(ap, void *);
                    s = conv_p2(ui_num, 4, 'x', &num_buf[NUM_BUF_SIZE], &s_len);
#endif
                    pad_char = ' ';
                    break;

                case NUL:
                    /*
                     * The last character of the format string was %.
                     * We ignore it.
                     */
                    continue;

                /*
                 * The default case is for unrecognized %'s. We print
                 * %<char> to help the user identify what option is not
                 * understood. This is also useful in case the user
                 * wants to pass the output of format_converter to
                 * another function that understands some other %<char>
                 * (like syslog). Note that we can't point s inside fmt
                 * because the unknown <char> could be preceded by width
                 * etc.
                 */
                default:
                    s = NULL;
                    if (vbuff->format != NULL) {
                        vbuff->format(vbuff,
                                      &prefix_char, &pad_char, &s, &s_len,
                                      num_buf, NUM_BUF_SIZE, extinfo, *fmt, (va_list *)(void *)&ap);
                        if (s == NULL)
                            return -1;
                    }
                    if (s == NULL) {
                        char_buf[0] = '%';
                        char_buf[1] = *fmt;
                        s = char_buf;
                        s_len = 2;
                        pad_char = ' ';
                    }
                    break;
            }

            if (prefix_char != NUL && s != S_NULL && s != char_buf) {
                *--s = prefix_char;
                s_len++;
            }

            if (adjust_width && adjust == RIGHT && min_width > s_len) {
                if (pad_char == '0' && prefix_char != NUL) {
                    INS_CHAR(*s, sp, bep, cc);
                    s++;
                    s_len--;
                    min_width--;
                }
                PAD(min_width, s_len, pad_char);
            }

            /*
             * Print the string s.
             */
            for (i = s_len; i != 0; i--) {
                INS_CHAR(*s, sp, bep, cc);
                s++;
            }

            if (adjust_width && adjust == LEFT && min_width > s_len)
                PAD(min_width, s_len, pad_char);
        }
        fmt++;
    }
    vbuff->curpos = sp;
    return cc;
}

/*
 * cfg_fmt_format -- format a new string.
 * This is inspired by POSIX sprintf(3), but mainly provides the
 * following differences: first it is actually a snprintf(3) style, i.e.
 * it allows one to specify the maximum number of characters which are
 * allowed to write. Second, it allows one to just count the number of
 * characters which have to be written.
 */

#define STR_FORMAT_BUFLEN 20

static int cfg_fmt_flush_fake(cfg_fmt_format_t *out_handle)
{
    out_handle->data[1].i += out_handle->data[2].i;
    out_handle->curpos = (char *)out_handle->data[0].vp;
    return 0;
}

static int cfg_fmt_flush_real(cfg_fmt_format_t *out_handle)
{
    return -1;
}

int cfg_fmt_vsprintf(char *s, size_t n, const char *fmt, va_list ap)
{
    cfg_fmt_format_t handle;
    char buf[STR_FORMAT_BUFLEN];
    int rv;

    if (n == 0)
        return 0;
    if (s == NULL) {
        /* fake formatting, i.e., calculate output length only */
        handle.curpos     = buf;
        handle.endpos     = buf + sizeof(buf) - 1;
        handle.flush      = cfg_fmt_flush_fake;
        handle.format     = NULL;
        handle.data[0].vp = buf;
        handle.data[1].i  = 0;
        handle.data[2].i  = sizeof(buf);
        rv = cfg_fmt_format(&handle, fmt, ap);
        if (rv == -1)
            rv = (int)n;
    }
    else {
        /* real formatting, i.e., create output */
        handle.curpos  = s;
        handle.endpos  = s + n - 1;
        handle.flush   = cfg_fmt_flush_real;
        handle.format  = NULL;
        rv = cfg_fmt_format(&handle, fmt, ap);
        *(handle.curpos) = NUL;
        if (rv == -1)
            rv = (int)n;
    }
    return rv;
}

char *cfg_fmt_vasprintf(const char *fmt, va_list ap)
{
    va_list apbak;
    char *s;
    int rv;

    va_copy(apbak, ap);
    if ((rv = cfg_fmt_vsprintf(NULL, -1, fmt, ap)) == -1)
        return NULL;
    if ((s = malloc(rv+1)) == NULL)
        return NULL;
    va_copy(ap, apbak);
    if ((rv = cfg_fmt_vsprintf(s, rv+1, fmt, ap)) == -1) {
        free(s);
        return NULL;
    }
    return s;
}

int cfg_fmt_sprintf(char *s, size_t n, const char *fmt, ...)
{
    va_list ap;
    int rv;

    va_start(ap, fmt);
    rv = cfg_fmt_vsprintf(s, n, fmt, ap);
    va_end(ap);
    return rv;
}

char *cfg_fmt_asprintf(const char *fmt, ...)
{
    va_list ap;
    char *rv;

    va_start(ap, fmt);
    rv = cfg_fmt_vasprintf(fmt, ap);
    va_end(ap);
    return rv;
}

