/*	$NetBSD: vsnprintf_ss.c,v 1.13 2014/09/29 14:58:33 christos Exp $
 */

/*-
 * Copyright (c) 1990, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Chris Torek.
 *
 * 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 University 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 REGENTS 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 REGENTS 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.
 */

#include <sys/cdefs.h>
#include <sys/types.h>
#include <inttypes.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <stdarg.h>
#include <string.h>
#include "reentrant.h"
#include "extern.h"
#include "local.h"

/*
 * vsnprintf_ss: scaled down version of printf(3).
 *
 * this version based on vfprintf() from libc which was derived from
 * software contributed to Berkeley by Chris Torek.
 *
 */

/*
 * macros for converting digits to letters and vice versa
 */
#define to_digit(c) ((c) - '0')
#define is_digit(c) ((unsigned)to_digit(c) <= 9)
#define to_char(n) (char)((n) + '0')

/*
 * flags used during conversion.
 */
#define ALT 0x001       /* alternate form */
#define HEXPREFIX 0x002 /* add 0x or 0X prefix */
#define LADJUST 0x004   /* left adjustment */
#define LONGDBL 0x008   /* long double; unimplemented */
#define LONGINT 0x010   /* long integer */
#define QUADINT 0x020   /* quad integer */
#define SHORTINT 0x040  /* short integer */
#define MAXINT 0x080    /* intmax_t */
#define PTRINT 0x100    /* intptr_t */
#define SIZEINT 0x200   /* size_t */
#define ZEROPAD 0x400   /* zero (as opposed to blank) pad */
#define FPT 0x800       /* Floating point number */

/*
 * To extend shorts properly, we need both signed and unsigned
 * argument extraction methods.
 */
#define SARG()                                                                 \
  (flags & MAXINT                                                              \
     ? va_arg(ap, intmax_t)                                                    \
     : flags & PTRINT                                                          \
         ? va_arg(ap, intptr_t)                                                \
         : flags & SIZEINT ? va_arg(ap, ssize_t) : /* XXX */                   \
             flags & QUADINT                                                   \
               ? va_arg(ap, quad_t)                                            \
               : flags & LONGINT                                               \
                   ? va_arg(ap, long)                                          \
                   : flags & SHORTINT ? (long)(short)va_arg(ap, int)           \
                                      : (long)va_arg(ap, int))
#define UARG()                                                                 \
  (flags & MAXINT                                                              \
     ? va_arg(ap, uintmax_t)                                                   \
     : flags & PTRINT                                                          \
         ? va_arg(ap, uintptr_t)                                               \
         : flags & SIZEINT                                                     \
             ? va_arg(ap, size_t)                                              \
             : flags & QUADINT                                                 \
                 ? va_arg(ap, u_quad_t)                                        \
                 : flags & LONGINT                                             \
                     ? va_arg(ap, u_long)                                      \
                     : flags & SHORTINT ? (u_long)(u_short)va_arg(ap, int)     \
                                        : (u_long)va_arg(ap, u_int))

#define PUTCHAR(C)                                                             \
  do {                                                                         \
    if (sbuf < tailp)                                                          \
      *sbuf++ = (C);                                                           \
  } while (/*CONSTCOND*/ 0)

int vsnprintf_ss(char *sbuf, size_t slen, const char *fmt0, va_list ap) {
  const char *fmt; /* format string */
  int ch;          /* character from fmt */
  int n;           /* handy integer (short term usage) */
  char *cp;        /* handy char pointer (short term usage) */
  int flags;       /* flags as above */
  int ret;         /* return value accumulator */
  int width;       /* width from format (%8d), or 0 */
  int prec;        /* precision from format (%.3d), or -1 */
  char sign;       /* sign prefix (' ', '+', '-', or \0) */

  u_quad_t _uquad;             /* integer arguments %[diouxX] */
  enum { OCT, DEC, HEX } base; /* base for [diouxX] conversion */
  int dprec;                   /* a copy of prec if [diouxX], 0 otherwise */
  int realsz;                  /* field size expanded by dprec */
  int size;                    /* size of converted field or string */
  const char *xdigs;           /* digits for [xX] conversion */
  char bf[128];                /* space for %c, %[diouxX] */
  char *tailp;                 /* tail pointer for snprintf */
  size_t len;

  static const char xdigs_lower[16] = "0123456789abcdef";
  static const char xdigs_upper[16] = "0123456789ABCDEF";

  _DIAGASSERT(slen == 0 || sbuf != NULL);
  _DIAGASSERT(fmt0 != NULL);

  if (slen > INT_MAX) {
    errno = EOVERFLOW;
    return -1;
  }

  tailp = sbuf + slen;

  cp = NULL; /* XXX: shutup gcc */
  size = 0;  /* XXX: shutup gcc */

  fmt = fmt0;
  ret = 0;

  xdigs = NULL; /* XXX: shut up gcc warning */

  /*
   * Scan the format for conversions (`%' character).
   */
  for (;;) {
    while (*fmt != '%' && *fmt) {
      ret++;
      PUTCHAR(*fmt);
      fmt++;
    }
    if (*fmt == 0)
      goto done;

    fmt++; /* skip over '%' */

    flags = 0;
    dprec = 0;
    width = 0;
    prec = -1;
    sign = '\0';

  rflag:
    ch = *fmt++;
  reswitch:
    switch (ch) {
      case ' ':
        /*
         * ``If the space and + flags both appear, the space
         * flag will be ignored.''
         *	-- ANSI X3J11
         */
        if (!sign)
          sign = ' ';
        goto rflag;
      case '#':
        flags |= ALT;
        goto rflag;
      case '*':
        /*
         * ``A negative field width argument is taken as a
         * - flag followed by a positive field width.''
         *	-- ANSI X3J11
         * They don't exclude field widths read from args.
         */
        if ((width = va_arg(ap, int)) >= 0)
          goto rflag;
        width = -width;
        /* FALLTHROUGH */
      case '-':
        flags |= LADJUST;
        goto rflag;
      case '+':
        sign = '+';
        goto rflag;
      case '.':
        if ((ch = *fmt++) == '*') {
          n = va_arg(ap, int);
          prec = n < 0 ? -1 : n;
          goto rflag;
        }
        n = 0;
        while (is_digit(ch)) {
          n = 10 * n + to_digit(ch);
          ch = *fmt++;
        }
        prec = n < 0 ? -1 : n;
        goto reswitch;
      case '0':
        /*
         * ``Note that 0 is taken as a flag, not as the
         * beginning of a field width.''
         *	-- ANSI X3J11
         */
        flags |= ZEROPAD;
        goto rflag;
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
        n = 0;
        do {
          n = 10 * n + to_digit(ch);
          ch = *fmt++;
        } while (is_digit(ch));
        width = n;
        goto reswitch;
      case 'h':
        flags |= SHORTINT;
        goto rflag;
      case 'j':
        flags |= MAXINT;
        goto rflag;
      case 'l':
        if (*fmt == 'l') {
          fmt++;
          flags |= QUADINT;
        } else {
          flags |= LONGINT;
        }
        goto rflag;
      case 'q':
        flags |= QUADINT;
        goto rflag;
      case 't':
        flags |= PTRINT;
        goto rflag;
      case 'z':
        flags |= SIZEINT;
        goto rflag;
      case 'c':
        *(cp = bf) = va_arg(ap, int);
        size = 1;
        sign = '\0';
        break;
      case 'D':
        flags |= LONGINT;
        /*FALLTHROUGH*/
      case 'd':
      case 'i':
        _uquad = SARG();
        if ((quad_t)_uquad < 0) {
          _uquad = -_uquad;
          sign = '-';
        }
        base = DEC;
        goto number;
      case 'n':
        if (flags & MAXINT)
          *va_arg(ap, intmax_t *) = ret;
        else if (flags & PTRINT)
          *va_arg(ap, intptr_t *) = ret;
        else if (flags & SIZEINT)
          *va_arg(ap, ssize_t *) = ret;
        else if (flags & QUADINT)
          *va_arg(ap, quad_t *) = ret;
        else if (flags & LONGINT)
          *va_arg(ap, long *) = ret;
        else if (flags & SHORTINT)
          *va_arg(ap, short *) = ret;
        else
          *va_arg(ap, int *) = ret;
        continue; /* no output */
      case 'O':
        flags |= LONGINT;
        /*FALLTHROUGH*/
      case 'o':
        _uquad = UARG();
        base = OCT;
        goto nosign;
      case 'p':
        /*
         * ``The argument shall be a pointer to void.  The
         * value of the pointer is converted to a sequence
         * of printable characters, in an implementation-
         * defined manner.''
         *	-- ANSI X3J11
         */
        /* NOSTRICT */
        _uquad = (u_long)va_arg(ap, void *);
        base = HEX;
        xdigs = xdigs_lower;
        flags |= HEXPREFIX;
        ch = 'x';
        goto nosign;
      case 's':
        if ((cp = va_arg(ap, char *)) == NULL)
          /*XXXUNCONST*/
          cp = __UNCONST("(null)");
        if (prec >= 0) {
          /*
           * can't use strlen; can only look for the
           * NUL in the first `prec' characters, and
           * strlen() will go further.
           */
          char *p = memchr(cp, 0, (size_t)prec);

          if (p != NULL) {
            _DIAGASSERT(__type_fit(int, p - cp));
            size = (int)(p - cp);
            if (size > prec)
              size = prec;
          } else
            size = prec;
        } else {
          len = strlen(cp);
          _DIAGASSERT(__type_fit(int, len));
          size = (int)len;
        }
        sign = '\0';
        break;
      case 'U':
        flags |= LONGINT;
        /*FALLTHROUGH*/
      case 'u':
        _uquad = UARG();
        base = DEC;
        goto nosign;
      case 'X':
        xdigs = xdigs_upper;
        goto hex;
      case 'x':
        xdigs = xdigs_lower;
      hex:
        _uquad = UARG();
        base = HEX;
        /* leading 0x/X only if non-zero */
        if (flags & ALT && _uquad != 0)
          flags |= HEXPREFIX;

        /* unsigned conversions */
      nosign:
        sign = '\0';
        /*
         * ``... diouXx conversions ... if a precision is
         * specified, the 0 flag will be ignored.''
         *	-- ANSI X3J11
         */
      number:
        if ((dprec = prec) >= 0)
          flags &= ~ZEROPAD;

        /*
         * ``The result of converting a zero value with an
         * explicit precision of zero is no characters.''
         *	-- ANSI X3J11
         */
        cp = bf + sizeof(bf);
        if (_uquad != 0 || prec != 0) {
          /*
           * Unsigned mod is hard, and unsigned mod
           * by a constant is easier than that by
           * a variable; hence this switch.
           */
          switch (base) {
            case OCT:
              do {
                *--cp = to_char(_uquad & 7);
                _uquad >>= 3;
              } while (_uquad);
              /* handle octal leading 0 */
              if (flags & ALT && *cp != '0')
                *--cp = '0';
              break;

            case DEC:
              /* many numbers are 1 digit */
              while (_uquad >= 10) {
                *--cp = to_char(_uquad % 10);
                _uquad /= 10;
              }
              *--cp = to_char(_uquad);
              break;

            case HEX:
              do {
                *--cp = xdigs[(size_t)_uquad & 15];
                _uquad >>= 4;
              } while (_uquad);
              break;

            default:
              /*XXXUNCONST*/
              cp = __UNCONST("bug bad base");
              len = strlen(cp);
              _DIAGASSERT(__type_fit(int, len));
              size = (int)len;
              goto skipsize;
          }
        }
        _DIAGASSERT(__type_fit(int, bf + sizeof(bf) - cp));
        size = (int)(bf + sizeof(bf) - cp);
      skipsize:
        break;
      default: /* "%?" prints ?, unless ? is NUL */
        if (ch == '\0')
          goto done;
        /* pretend it was %c with argument ch */
        cp = bf;
        *cp = ch;
        size = 1;
        sign = '\0';
        break;
    }

    /*
     * All reasonable formats wind up here.  At this point, `cp'
     * points to a string which (if not flags&LADJUST) should be
     * padded out to `width' places.  If flags&ZEROPAD, it should
     * first be prefixed by any sign or other prefix; otherwise,
     * it should be blank padded before the prefix is emitted.
     * After any left-hand padding and prefixing, emit zeroes
     * required by a decimal [diouxX] precision, then print the
     * string proper, then emit zeroes required by any leftover
     * floating precision; finally, if LADJUST, pad with blanks.
     *
     * Compute actual size, so we know how much to pad.
     * size excludes decimal prec; realsz includes it.
     */
    realsz = dprec > size ? dprec : size;
    if (sign)
      realsz++;
    else if (flags & HEXPREFIX)
      realsz += 2;

    /* adjust ret */
    ret += width > realsz ? width : realsz;

    /* right-adjusting blank padding */
    if ((flags & (LADJUST | ZEROPAD)) == 0) {
      n = width - realsz;
      while (n-- > 0)
        PUTCHAR(' ');
    }

    /* prefix */
    if (sign) {
      PUTCHAR(sign);
    } else if (flags & HEXPREFIX) {
      PUTCHAR('0');
      PUTCHAR(ch);
    }

    /* right-adjusting zero padding */
    if ((flags & (LADJUST | ZEROPAD)) == ZEROPAD) {
      n = width - realsz;
      while (n-- > 0)
        PUTCHAR('0');
    }

    /* leading zeroes from decimal precision */
    n = dprec - size;
    while (n-- > 0)
      PUTCHAR('0');

    /* the string or number proper */
    while (size--)
      PUTCHAR(*cp++);
    /* left-adjusting padding (always blank) */
    if (flags & LADJUST) {
      n = width - realsz;
      while (n-- > 0)
        PUTCHAR(' ');
    }
  }

done:
  if (sbuf == tailp)
    sbuf[-1] = '\0';
  else
    *sbuf = '\0';
  return ret;
  /* NOTREACHED */
}
