
//-----------------------------------------------------------------------------
// File:        FormattedPrint.cpp
//
//
// Contents:    Contains functions for handling Windows format strings
//              and UTF-16 on non-Windows platforms
//
// Microsoft Drivers 5.8 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), 
//  to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
//  and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
//  IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------

#include <FormattedPrint.h>
#include <errno.h>

#include <iconv.h>

#include "StringFunctions.h"

// XPLAT_ODBC_TODO VSTS 819733 - MPlat: Reconcile std c++ usage between platforms
#include <vector>
#include <algorithm>
#include "sal_def.h"

#define PTR_IS_INT64 1

// SQL Server does not have a long double type
#define LONGDOUBLE_IS_DOUBLE 1
typedef double _LONGDOUBLE;

// XPLAT_ODBC_TODO VSTS VSTS 718708 Localization
#define _SAFECRT_IMPL

#if !defined(_countof)
#define _countof(_Array) (sizeof(_Array) / sizeof(_Array[0]))
#endif // _countof

#ifndef _VALIDATE_RETURN
#define _VALIDATE_RETURN( expr, errorcode, retexpr )                           \
    {                                                                          \
        int _Expr_val=!!(expr);                                                \
        if ( !( _Expr_val ) )                                                  \
        {                                                                      \
            assert(false);                                                     \
            errno = errorcode;                                                 \
            return ( retexpr );                                                \
        }                                                                      \
    }
#endif  /* _VALIDATE_RETURN */


static const char *__nullstring = "(null)";  /* string to print on null ptr */
static const wchar_t *__wnullstring = L"(null)";/* string to print on null ptr */

#define BUFFERSIZE    512
#define MAXPRECISION  BUFFERSIZE
#define _CVTBUFSIZE (309+40) /* # of digits in max. dp value + slop */


/* flag definitions */
#define FL_SIGN       0x00001   /* put plus or minus in front */
#define FL_SIGNSP     0x00002   /* put space or minus in front */
#define FL_LEFT       0x00004   /* left justify */
#define FL_LEADZERO   0x00008   /* pad with leading zeros */
#define FL_LONG       0x00010   /* long value given */
#define FL_SHORT      0x00020   /* short value given */
#define FL_SIGNED     0x00040   /* signed data given */
#define FL_ALTERNATE  0x00080   /* alternate form requested */
#define FL_NEGATIVE   0x00100   /* value is negative */
#define FL_FORCEOCTAL 0x00200   /* force leading '0' for octals */
#define FL_LONGDOUBLE 0x00400   /* long double value given */
#define FL_WIDECHAR   0x00800   /* wide characters */
#define FL_LONGLONG   0x01000   /* long long value given */
#define FL_I64        0x08000   /* __int64 value given */


/* state definitions */
enum STATE {
    ST_NORMAL,          /* normal state; outputting literal chars */
    ST_PERCENT,         /* just read '%' */
    ST_FLAG,            /* just read flag character */
    ST_WIDTH,           /* just read width specifier */
    ST_DOT,             /* just read '.' */
    ST_PRECIS,          /* just read precision specifier */
    ST_SIZE,            /* just read size specifier */
    ST_TYPE             /* just read type specifier */
    ,ST_INVALID           /* Invalid format */

};

#define NUMSTATES (ST_INVALID + 1)

/* character type values */
enum CHARTYPE {
    CH_OTHER,           /* character with no special meaning */
    CH_PERCENT,         /* '%' */
    CH_DOT,             /* '.' */
    CH_STAR,            /* '*' */
    CH_ZERO,            /* '0' */
    CH_DIGIT,           /* '1'..'9' */
    CH_FLAG,            /* ' ', '+', '-', '#' */
    CH_SIZE,            /* 'h', 'l', 'L', 'N', 'F', 'w' */
    CH_TYPE             /* type specifying character */
};


static const unsigned char __lookuptable_s[] = {
 /* ' ' */  0x06,
 /* '!' */  0x80,
 /* '"' */  0x80,
 /* '#' */  0x86,
 /* '$' */  0x80,
 /* '%' */  0x81,
 /* '&' */  0x80,
 /* ''' */  0x00,
 /* '(' */  0x00,
 /* ')' */  0x10,
 /* '*' */  0x03,
 /* '+' */  0x86,
 /* ',' */  0x80,
 /* '-' */  0x86,
 /* '.' */  0x82,
 /* '/' */  0x80,
 /* '0' */  0x14,
 /* '1' */  0x05,
 /* '2' */  0x05,
 /* '3' */  0x45,
 /* '4' */  0x45,
 /* '5' */  0x45,
 /* '6' */  0x85,
 /* '7' */  0x85,
 /* '8' */  0x85,
 /* '9' */  0x05,
 /* ':' */  0x00,
 /* ';' */  0x00,
 /* '<' */  0x30,
 /* '=' */  0x30,
 /* '>' */  0x80,
 /* '?' */  0x50,
 /* '@' */  0x80,
#if defined (_SAFECRT_IMPL)
 /* 'A' */  0x80,       // Disable %A format
#else  /* defined (_SAFECRT_IMPL) */
 /* 'A' */  0x88,
#endif  /* defined (_SAFECRT_IMPL) */
 /* 'B' */  0x00,
 /* 'C' */  0x08,
 /* 'D' */  0x00,
 /* 'E' */  0x28,
 /* 'F' */  0x27,
 /* 'G' */  0x38,
 /* 'H' */  0x50,
 /* 'I' */  0x57,
 /* 'J' */  0x80,
 /* 'K' */  0x00,
 /* 'L' */  0x07,
 /* 'M' */  0x00,
 /* 'N' */  0x37,
 /* 'O' */  0x30,
 /* 'P' */  0x30,
 /* 'Q' */  0x50,
 /* 'R' */  0x50,
 /* 'S' */  0x88,
 /* 'T' */  0x00,
 /* 'U' */  0x00,
 /* 'V' */  0x00,
 /* 'W' */  0x20,
 /* 'X' */  0x28,
 /* 'Y' */  0x80,
 /* 'Z' */  0x88,
 /* '[' */  0x80,
 /* '\' */  0x80,
 /* ']' */  0x00,
 /* '^' */  0x00,
 /* '_' */  0x00,
 /* '`' */  0x60,
#if defined (_SAFECRT_IMPL)
 /* 'a' */  0x60,       // Disable %a format
#else  /* defined (_SAFECRT_IMPL) */
 /* 'a' */  0x68,
#endif  /* defined (_SAFECRT_IMPL) */
 /* 'b' */  0x60,
 /* 'c' */  0x68,
 /* 'd' */  0x68,
 /* 'e' */  0x68,
 /* 'f' */  0x08,
 /* 'g' */  0x08,
 /* 'h' */  0x07,
 /* 'i' */  0x78,
 /* 'j' */  0x70,
 /* 'k' */  0x70,
 /* 'l' */  0x77,
 /* 'm' */  0x70,
 /* 'n' */  0x70,
 /* 'o' */  0x08,
 /* 'p' */  0x08,
 /* 'q' */  0x00,
 /* 'r' */  0x00,
 /* 's' */  0x08,
 /* 't' */  0x00,
 /* 'u' */  0x08,
 /* 'v' */  0x00,
 /* 'w' */  0x07,
 /* 'x' */  0x08
};

static inline CHARTYPE GetCharType( char ch )
{
    return ((ch < (' ') || ch > ('x')) ? CH_OTHER : (enum CHARTYPE)(__lookuptable_s[ch - (' ')] & 0xF));
}
static inline STATE GetState( CHARTYPE type, STATE oldState )
{
    return (enum STATE)(__lookuptable_s[type * NUMSTATES + oldState] >> 4);
}


static bool isleadbyte(unsigned char ch)
{
    return (FALSE != IsDBCSLeadByte(ch) );
}
static bool _isleadbyte_l(unsigned char ch, _locale_t loc)
{
    // XPLAT_ODBC_TODO VSTS 718708 Localization
    return ( FALSE != IsDBCSLeadByte(ch) );
}



#define _WCTOMB_S mplat_wctomb_s
errno_t mplat_wctomb_s(
   int *pRetValue,
   char *mbchar,
   size_t sizeInBytes,
   WCHAR wchar
)
{
    DWORD rc;
    size_t cch = SystemLocale::FromUtf16( CP_ACP, &wchar, 1, mbchar, sizeInBytes, NULL, &rc );
    *pRetValue = (int)cch;
    return (ERROR_SUCCESS == rc ? 0 : -1);
}

// Floating point print routines
void _CFLTCVT( double * dbl, char * buf, int bufSize, char fmt, int precision, int caps, _locale_t loc = NULL )
{
    const size_t local_bufsize = 8;
    char local_fmt[local_bufsize] = {'\0'};

    if ( 0 != caps )
    {
        fmt -= ('a') - ('A');    /* convert format char to upper */
    }
    int chars_printed = snprintf( local_fmt, local_bufsize, "%%.%d%c", precision-1, fmt );
    assert( 0 < chars_printed && (size_t)chars_printed < local_bufsize );

    // We want to use the platform version of snprintf so temporarily undef.
    // Formatting of floating pt values is complex so we didn't implement it here.
    // Even porting the CRT code would've been difficult.  Instead, we can use the
    // platform's snprintf for just floating pt values.  We have to undef to prevent
    // recursing right back to here.
#   undef snprintf
    chars_printed = snprintf( buf, bufSize, local_fmt, *dbl );
    assert( 0 < chars_printed && chars_printed < bufSize );
#   define snprintf mplat_snprintf
}

#if !LONGDOUBLE_IS_DOUBLE
// SQL Server does not support the long double data type so this should never be called.
// It will be compiled out on Linux.
void _CLDCVT( _LONGDOUBLE * dbl, char * buf, int bufSize, char fmt, int precision, int caps )
{
    assert(false);
}
#endif

static enum STATE ProcessSizeA( char sizeCh, char fmt_ch, char next_fmt_ch, int * advance, int * flags )
{
    *advance = 0;
    switch (sizeCh)
    {
    case 'l':
        /*
        * In order to handle the ll case, we depart from the
        * simple deterministic state machine.
        */
        if ( 'l' == fmt_ch )
        {
            *advance = 1;
            *flags |= FL_LONGLONG;
        }
        else
        {
            *flags |= FL_LONG;
        }
        break;

    case 'I':
        /*
        * In order to handle the I, I32, and I64 size modifiers, we
        * depart from the simple deterministic state machine. The
        * code below scans for characters following the 'I',
        * and defaults to 64 bit on WIN64 and 32 bit on WIN32
        */
#if PTR_IS_INT64
        *flags |= FL_I64;    /* 'I' => __int64 on WIN64 systems */
#endif  /* PTR_IS_INT64 */
        if ( '6' == fmt_ch && '4' == next_fmt_ch )
        {
            *advance = 2;
            *flags |= FL_I64;    /* I64 => __int64 */
        }
        else if ( '3' == fmt_ch && '2' == next_fmt_ch )
        {
            *advance = 2;
            *flags &= ~FL_I64;   /* I32 => __int32 */
        }
        else if (
            (fmt_ch == 'd') ||
            (fmt_ch == 'i') ||
            (fmt_ch == 'o') ||
            (fmt_ch == 'u') ||
            (fmt_ch == 'x') ||
            (fmt_ch == 'X') )
        {
            /*
            * Nothing further needed.  %Id (et al) is
            * handled just like %d, except that it defaults to 64 bits
            * on WIN64.  Fall through to the next iteration.
            */
        }
        else
        {
            return ST_NORMAL;
        }
        break;

    case 'h':
        *flags |= FL_SHORT;
        break;

    case 'w':
        *flags |= FL_WIDECHAR;
    }

    return ST_SIZE;
}

STATE ProcessSize( char sizeCh, const char * format, int * advance, int * flags )
{
    char formatCh = *format;
    char next_formatCh = ('\0' == formatCh ? '\0' : *(format+1));
    return ProcessSizeA( sizeCh, formatCh, next_formatCh, advance, flags );
}

// Tools\vc\src\crt\amd64\output.c
int FormattedPrintA( IFormattedPrintOutput<char> * output, const char *format, va_list argptr )
{
    int hexadd=0;          /* offset to add to number to get 'a'..'f' */
    char ch;               /* character just read */
    int flags=0;           /* flag word -- see #defines above for flag values */
    enum STATE state;      /* current state */
    enum CHARTYPE chclass; /* class of current character */
    int radix;             /* current conversion radix */
    int charsout;          /* characters currently written so far, -1 = IO error */
    int fldwidth = 0;      /* selected field width -- 0 means default */
    int precision = 0;     /* selected precision  -- -1 means default */
    char prefix[2];        /* numeric prefix -- up to two characters */
    int prefixlen=0;       /* length of prefix -- 0 means no prefix */
    int capexp=0;          /* non-zero = 'E' exponent signifient, zero = 'e' or unused */
    int no_output=0;       /* non-zero = prodcue no output for this specifier */
    union {
        char *sz;   /* pointer text to be printed, not zero terminated */
        WCHAR *wz;
        } text;

    int textlen;    /* length of the text in bytes/wchars to be printed.
                       textlen is in multibyte or wide chars if _UNICODE */
    union {
        char sz[BUFFERSIZE];
        } buffer = {'\0'};
    WCHAR wchar;                /* temp wchar_t */
    int buffersize;             /* size of text.sz (used only for the call to _cfltcvt) */
    int bufferiswide=0;         /* non-zero = buffer contains wide chars already */

#ifndef _SAFECRT_IMPL
    _LocaleUpdate _loc_update(plocinfo);
#endif  /* _SAFECRT_IMPL */

    char *heapbuf = NULL; /* non-zero = test.sz using heap buffer to be freed */

    int advance; /* count of how much helper fxns need format ptr incremented */

    _VALIDATE_RETURN( ((output != NULL) && (format != NULL)), EINVAL, -1);

    charsout = 0;       /* no characters written yet */
    textlen = 0;        /* no text yet */
    state = ST_NORMAL;  /* starting state */
    heapbuf = NULL;     /* not using heap-allocated buffer */
    buffersize = 0;

    /* main loop -- loop while format character exist and no I/O errors */
    while ((ch = *format++) != '\0' && charsout >= 0) {
        // Find char class and next state
        chclass = GetCharType( ch );
        state = GetState( chclass, state );

        /* execute code for each state */
        switch (state) {

        case ST_INVALID:
            // "Incorrect format specifier"
            assert( false );
            errno = EINVAL;
            return -1;

        case ST_NORMAL:

        NORMAL_STATE:

            /* normal state -- just write character */
            bufferiswide = 0;
#ifdef _SAFECRT_IMPL
            if (isleadbyte((unsigned char)ch)) {
#else  /* _SAFECRT_IMPL */
            if (_isleadbyte_l((unsigned char)ch, _loc_update.GetLocaleT())) {
#endif  /* _SAFECRT_IMPL */
                // XPLAT_ODBC_TODO VSTS 718708 Localization
                // Deal with more than 2 storage units per character
                output->WRITE_CHAR(ch, &charsout);
                ch = *format++;
                /* don't fall off format string */
                _VALIDATE_RETURN( (ch != '\0'), EINVAL, -1);
            }
            output->WRITE_CHAR(ch, &charsout);
            break;

        case ST_PERCENT:
            /* set default value of conversion parameters */
            prefixlen = fldwidth = no_output = capexp = 0;
            flags = 0;
            precision = -1;
            bufferiswide = 0;   /* default */
            break;

        case ST_FLAG:
            /* set flag based on which flag character */
            switch (ch) {
            case ('-'):
                flags |= FL_LEFT;   /* '-' => left justify */
                break;
            case ('+'):
                flags |= FL_SIGN;   /* '+' => force sign indicator */
                break;
            case (' '):
                flags |= FL_SIGNSP; /* ' ' => force sign or space */
                break;
            case ('#'):
                flags |= FL_ALTERNATE;  /* '#' => alternate form */
                break;
            case ('0'):
                flags |= FL_LEADZERO;   /* '0' => pad with leading zeros */
                break;
            }
            break;

        case ST_WIDTH:
            /* update width value */
            if (ch == ('*')) {
                /* get width from arg list */
                fldwidth = va_arg(argptr, int);
                if (fldwidth < 0) {
                    /* ANSI says neg fld width means '-' flag and pos width */
                    flags |= FL_LEFT;
                    fldwidth = -fldwidth;
                }
            }
            else {
                /* add digit to current field width */
                fldwidth = fldwidth * 10 + (ch - ('0'));
            }
            break;

        case ST_DOT:
            /* zero the precision, since dot with no number means 0
               not default, according to ANSI */
            precision = 0;
            break;

        case ST_PRECIS:
            /* update precison value */
            if (ch == ('*')) {
                /* get precision from arg list */
                precision = va_arg(argptr, int);
                if (precision < 0)
                    precision = -1; /* neg precision means default */
            }
            else {
                /* add digit to current precision */
                precision = precision * 10 + (ch - ('0'));
            }
            break;

        case ST_SIZE:
            /* just read a size specifier, set the flags based on it */
            state = ProcessSize( ch, format, &advance, &flags );
            format += advance;
            if ( ST_NORMAL == state )
            {
                goto NORMAL_STATE;
            }
            break;

        case ST_TYPE:
            /* we have finally read the actual type character, so we       */
            /* now format and "print" the output.  We use a big switch     */
            /* statement that sets 'text' to point to the text that should */
            /* be printed, and 'textlen' to the length of this text.       */
            /* Common code later on takes care of justifying it and        */
            /* other miscellaneous chores.  Note that cases share code,    */
            /* in particular, all integer formatting is done in one place. */
            /* Look at those funky goto statements!                        */

            switch (ch) {

            case ('C'):   /* ISO wide character */
                if (!(flags & (FL_SHORT|FL_LONG|FL_WIDECHAR)))
                    flags |= FL_WIDECHAR;   /* ISO std. */
                /* fall into 'c' case */

            case ('c'): {
                /* print a single character specified by int argument */
                if (flags & (FL_LONG|FL_WIDECHAR)) {
                    errno_t e = 0;
                    wchar = (WCHAR) va_arg(argptr, int);
                    /* convert to multibyte character */
                    e = _WCTOMB_S(&textlen, buffer.sz, _countof(buffer.sz), wchar);

                    /* check that conversion was successful */
                    if (e != 0)
                        no_output = 1;
                } else {
                    /* format multibyte character */
                    /* this is an extension of ANSI */
                    unsigned short temp;
                    temp = (unsigned short) va_arg(argptr, int);
                    {
                        buffer.sz[0] = (char) temp;
                        textlen = 1;
                    }
                }
                text.sz = buffer.sz;
            }
            break;

            case ('Z'): {
                // 'Z' format specifier disabled
                _VALIDATE_RETURN(0, EINVAL, -1);
            }
            break;

            case ('S'):   /* ISO wide character string */
                if (!(flags & (FL_SHORT|FL_LONG|FL_WIDECHAR)))
                    flags |= FL_WIDECHAR;

            case ('s'): {
                /* print a string --                            */
                /* ANSI rules on how much of string to print:   */
                /*   all if precision is default,               */
                /*   min(precision, length) if precision given. */
                /* prints '(null)' if a null string is passed   */

                int i;
                char *p;       /* temps */
                WCHAR *pwch;

                /* At this point it is tempting to use strlen(), but */
                /* if a precision is specified, we're not allowed to */
                /* scan past there, because there might be no null   */
                /* at all.  Thus, we must do our own scan.           */

                i = (precision == -1) ? INT_MAX : precision;
                text.sz = (char *)va_arg(argptr, void *);

                /* scan for null upto i characters */
                if (flags & (FL_LONG|FL_WIDECHAR)) {
                    if (text.wz == NULL) /* NULL passed, use special string */
                        text.wz = (WCHAR *)__wnullstring;
                    bufferiswide = 1;
                    pwch = text.wz;
                    while ( i-- && *pwch )
                        ++pwch;
                    textlen = (int)(pwch - text.wz);
                    /* textlen now contains length in wide chars */
                } else {
                    if (text.sz == NULL) /* NULL passed, use special string */
                        text.sz = (char*) __nullstring;
                    p = text.sz;
                    while (i-- && *p)
                        ++p;
                    textlen = (int)(p - text.sz);    /* length of the string */
                }
            }
            break;


            case ('n'): {
                // We will not support %n
                _VALIDATE_RETURN(0, EINVAL, -1);
            }
            break;

            case ('E'):
            case ('G'):
            case ('A'):
                capexp = 1;                 /* capitalize exponent */
                ch += ('a') - ('A');    /* convert format char to lower */
                /* DROP THROUGH */
            case ('e'):
            case ('f'):
            case ('g'):
            case ('a'): {
                /* floating point conversion -- we call cfltcvt routines */
                /* to do the work for us.                                */
                flags |= FL_SIGNED;         /* floating point is signed conversion */
                text.sz = buffer.sz;        /* put result in buffer */
                buffersize = BUFFERSIZE;

                /* compute the precision value */
                if (precision < 0)
                    precision = 6;          /* default precision: 6 */
                else if (precision == 0 && ch == ('g'))
                    precision = 1;          /* ANSI specified */
                else if (precision > MAXPRECISION)
                    precision = MAXPRECISION;

                if (precision > BUFFERSIZE - _CVTBUFSIZE) {
                    /* conversion will potentially overflow local buffer */
                    /* so we need to use a heap-allocated buffer.        */
                    heapbuf = (char *)malloc(_CVTBUFSIZE + precision);
                    if (heapbuf != NULL)
                    {
                        text.sz = heapbuf;
                        buffersize = _CVTBUFSIZE + precision;
                    }
                    else
                        /* malloc failed, cap precision further */
                        precision = BUFFERSIZE - _CVTBUFSIZE;
                }

#ifdef _SAFECRT_IMPL
                /* for safecrt, we pass along the FL_ALTERNATE flag to _safecrt_cfltcvt */
                if (flags & FL_ALTERNATE)
                {
                    capexp |= FL_ALTERNATE;
                }
#endif  /* _SAFECRT_IMPL */

#if !LONGDOUBLE_IS_DOUBLE
                /* do the conversion */
                if (flags & FL_LONGDOUBLE) {
                    _LONGDOUBLE tmp;
                    tmp=va_arg(argptr, _LONGDOUBLE);
                    /* Note: assumes ch is in ASCII range */
                    _CLDCVT(&tmp, text.sz, buffersize, (char)ch, precision, capexp);
                } else
#endif  /* !LONGDOUBLE_IS_DOUBLE */
                {
                    double tmp;
                    tmp=va_arg(argptr, double);
                    /* Note: assumes ch is in ASCII range */
                    /* In safecrt, we provide a special version of _cfltcvt which internally calls printf (see safecrt_output_s.c) */
#ifndef _SAFECRT_IMPL
                    _cfltcvt_l(&tmp, text.sz, buffersize, (char)ch, precision, capexp, _loc_update.GetLocaleT());
#else  /* _SAFECRT_IMPL */
                    _CFLTCVT(&tmp, text.sz, buffersize, (char)ch, precision, capexp);
#endif  /* _SAFECRT_IMPL */
                }

#ifndef _SAFECRT_IMPL
                /* For safecrt, this is done already in _safecrt_cfltcvt */

                /* '#' and precision == 0 means force a decimal point */
                if ((flags & FL_ALTERNATE) && precision == 0)
                {
                    _forcdecpt_l(text.sz, _loc_update.GetLocaleT());
                }

                /* 'g' format means crop zero unless '#' given */
                if (ch == ('g') && !(flags & FL_ALTERNATE))
                {
                    _cropzeros_l(text.sz, _loc_update.GetLocaleT());
                }
#endif  /* _SAFECRT_IMPL */

                /* check if result was negative, save '-' for later */
                /* and point to positive part (this is for '0' padding) */
                if (*text.sz == '-') {
                    flags |= FL_NEGATIVE;
                    ++text.sz;
                }

                textlen = (int)strnlen_s(text.sz);     /* compute length of text */
            }
            break;

            case ('d'):
            case ('i'):
                /* signed decimal output */
                flags |= FL_SIGNED;
                radix = 10;
                goto COMMON_INT;

            case ('u'):
                radix = 10;
                goto COMMON_INT;

            case ('p'):
                /* write a pointer -- this is like an integer or long */
                /* except we force precision to pad with zeros and */
                /* output in big hex. */

                precision = 2 * sizeof(void *);     /* number of hex digits needed */
#if PTR_IS_INT64
                flags |= FL_I64;                    /* assume we're converting an int64 */
#endif  /* !PTR_IS_INT */
                /* DROP THROUGH to hex formatting */

            case ('X'):
                /* unsigned upper hex output */
                hexadd = ('A') - ('9') - 1;     /* set hexadd for uppercase hex */
                goto COMMON_HEX;

            case ('x'):
                /* unsigned lower hex output */
                hexadd = ('a') - ('9') - 1;     /* set hexadd for lowercase hex */
                /* DROP THROUGH TO COMMON_HEX */

            COMMON_HEX:
                radix = 16;
                if (flags & FL_ALTERNATE) {
                    /* alternate form means '0x' prefix */
                    prefix[0] = ('0');
                    prefix[1] = (char)(('x') - ('a') + ('9') + 1 + hexadd);  /* 'x' or 'X' */
                    prefixlen = 2;
                }
                goto COMMON_INT;

            case ('o'):
                /* unsigned octal output */
                radix = 8;
                if (flags & FL_ALTERNATE) {
                    /* alternate form means force a leading 0 */
                    flags |= FL_FORCEOCTAL;
                }
                /* DROP THROUGH to COMMON_INT */

            COMMON_INT: {
                /* This is the general integer formatting routine. */
                /* Basically, we get an argument, make it positive */
                /* if necessary, and convert it according to the */
                /* correct radix, setting text and textlen */
                /* appropriately. */

                ULONGLONG number;    /* number to convert */
                int digit;              /* ascii value of digit */
                LONGLONG l;              /* temp long value */

                /* 1. read argument into l, sign extend as needed */
                if (flags & FL_I64)
                    l = va_arg(argptr, LONGLONG);
                else if (flags & FL_LONGLONG)
                    l = va_arg(argptr, LONGLONG);
                else

                if (flags & FL_SHORT) {
                    if (flags & FL_SIGNED)
                        l = (short) va_arg(argptr, int); /* sign extend */
                    else
                        l = (unsigned short) va_arg(argptr, int);    /* zero-extend*/

                } else
                {
                    if (flags & FL_SIGNED)
                        l = (int)va_arg(argptr, int); /* sign extend */
                    else
                        l = (unsigned int) va_arg(argptr, int);    /* zero-extend*/
                }

                /* 2. check for negative; copy into number */
                if ( (flags & FL_SIGNED) && l < 0) {
                    number = -l;
                    flags |= FL_NEGATIVE;   /* remember negative sign */
                } else {
                    number = l;
                }

                if ( (flags & FL_I64) == 0 && (flags & FL_LONGLONG) == 0 ) {
                    /*
                     * Unless printing a full 64-bit value, insure values
                     * here are not in cananical longword format to prevent
                     * the sign extended upper 32-bits from being printed.
                     */
                    number &= 0xffffffff;
                }

                /* 3. check precision value for default; non-default */
                /*    turns off 0 flag, according to ANSI. */
                if (precision < 0)
                    precision = 1;  /* default precision */
                else {
                    flags &= ~FL_LEADZERO;
                    if (precision > MAXPRECISION)
                        precision = MAXPRECISION;
                }

                /* 4. Check if data is 0; if so, turn off hex prefix */
                if (number == 0)
                    prefixlen = 0;

                /* 5. Convert data to ASCII -- note if precision is zero */
                /*    and number is zero, we get no digits at all.       */

                text.sz = &buffer.sz[BUFFERSIZE-1];    /* last digit at end of buffer */

                while (precision-- > 0 || number != 0) {
                    digit = (int)(number % radix) + '0';
                    number /= radix;                /* reduce number */
                    if (digit > '9') {
                        /* a hex digit, make it a letter */
                        digit += hexadd;
                    }
                    *text.sz-- = (char)digit;       /* store the digit */
                }

                textlen = (int)((char *)&buffer.sz[BUFFERSIZE-1] - text.sz); /* compute length of number */
                ++text.sz;          /* text points to first digit now */


                /* 6. Force a leading zero if FORCEOCTAL flag set */
                if ((flags & FL_FORCEOCTAL) && (textlen == 0 || text.sz[0] != '0')) {
                    *--text.sz = '0';
                    ++textlen;      /* add a zero */
                }
            }
            break;
            }

            /* At this point, we have done the specific conversion, and */
            /* 'text' points to text to print; 'textlen' is length.  Now we */
            /* justify it, put on prefixes, leading zeros, and then */
            /* print it. */

            if (!no_output) {
                int padding;    /* amount of padding, negative means zero */

                if (flags & FL_SIGNED) {
                    if (flags & FL_NEGATIVE) {
                        /* prefix is a '-' */
                        prefix[0] = ('-');
                        prefixlen = 1;
                    }
                    else if (flags & FL_SIGN) {
                        /* prefix is '+' */
                        prefix[0] = ('+');
                        prefixlen = 1;
                    }
                    else if (flags & FL_SIGNSP) {
                        /* prefix is ' ' */
                        prefix[0] = (' ');
                        prefixlen = 1;
                    }
                }

                /* calculate amount of padding -- might be negative, */
                /* but this will just mean zero */
                padding = fldwidth - textlen - prefixlen;

                /* put out the padding, prefix, and text, in the correct order */

                if (!(flags & (FL_LEFT | FL_LEADZERO))) {
                    /* pad on left with blanks */
                    output->WRITE_MULTI_CHAR((' '), padding, &charsout);
                }

                /* write prefix */
                output->WRITE_STRING(prefix, prefixlen, &charsout);

                if ((flags & FL_LEADZERO) && !(flags & FL_LEFT)) {
                    /* write leading zeros */
                    output->WRITE_MULTI_CHAR(('0'), padding, &charsout);
                }

                /* write text */
                if (bufferiswide && (textlen > 0)) {
                    WCHAR *p;
                    int retval, count;
                    errno_t e = 0;
                    char L_buffer[MB_LEN_MAX+1] = {'\0'};

                    p = text.wz;
                    count = textlen;
                    while (count--) {
                        e = _WCTOMB_S(&retval, L_buffer, _countof(L_buffer), *p++);
                        if (e != 0 || retval == 0) {
                            charsout = -1;
                            break;
                        }
                        output->WRITE_STRING(L_buffer, retval, &charsout);
                    }
                } else {
                    output->WRITE_STRING(text.sz, textlen, &charsout);
                }

                if (charsout >= 0 && (flags & FL_LEFT)) {
                    /* pad on right with blanks */
                    output->WRITE_MULTI_CHAR((' '), padding, &charsout);
                }

                /* we're done! */
            }
            if (heapbuf) {
                free(heapbuf);
                heapbuf = NULL;
            }
            break;
        }
    }

    /* The format string shouldn't be incomplete - i.e. when we are finished
        with the format string, the last thing we should have encountered
        should have been a regular char to be output or a type specifier. Else
        the format string was incomplete */
    _VALIDATE_RETURN(((state == ST_NORMAL) || (state == ST_TYPE)), EINVAL, -1);

    return charsout;        /* return value = number of characters written */
}

// Used for holding the size and value of a variable argument.
// Uses INT and LONGLONG to hold all possible values.  Each is just a buffer to hold the right number of bits.
struct vararg_t
{
    enum ArgType_e
    {
        Unknown,
        Int32,
        Int64,
        ShouldBeInt32,
        ShouldBeInt64
    };

    vararg_t() : int64Val(0), int32Val(0), argType(vararg_t::Unknown) {}
    vararg_t( INT val ) : int64Val(0), int32Val(val), argType(vararg_t::Int32) {}
    vararg_t( LONGLONG ptr ) : int64Val(ptr), int32Val(0), argType(vararg_t::Int64) {}

    ArgType_e Type() const { return argType; }
    INT Int32Value() const { return int32Val; }
    LONGLONG Int64Value() const { return int64Val; }
    void * PtrValue() const
    {
#if PTR_IS_INT64
        return reinterpret_cast<void *>(int64Val);
#else
        return reinterpret_cast<void *>(int32Val);
#endif
    }

    void SetForInt32()
    {
        assert( vararg_t::Unknown == argType );
        argType = vararg_t::ShouldBeInt32;
    }
    void SetForInt64()
    {
        assert( vararg_t::Unknown == argType );
        argType = vararg_t::ShouldBeInt64;
    }
    void SetForPtr()
    {
#if PTR_IS_INT64
        SetForInt64();
#else
        SetForInt32();
#endif
    }

    void Int32Value( INT val )
    {
        assert( vararg_t::Unknown == argType || vararg_t::ShouldBeInt32 == argType );
        assert( 0 == int64Val );
        argType = vararg_t::Int32;
        int32Val = val;
    }
    void Int64Value( LONGLONG val )
    {
        assert( vararg_t::Unknown == argType || vararg_t::ShouldBeInt64 == argType );
        assert( 0 == int32Val );
        argType = vararg_t::Int64;
        int64Val = val;
    }

private:
    LONGLONG int64Val;
    INT int32Val;
    ArgType_e argType;
};

// Caches the var arg values in the supplied vector.  Types are determined by inspecting the format string.
// On error, sets errno and returns false
static bool GetFormatMessageArgsA( const char * format, std::vector< vararg_t > * argcache, va_list * Arguments )
{
    if ( NULL == format )
    {
        errno = EINVAL;
        return false;
    }

    const char *p = format;
    char fmt_ch;

    while( '\0' != (fmt_ch = *p++) )
    {
        if ( '%' != fmt_ch )
        {
            // continue to next format spec
        }
        else if ( '0' == *p || '\0' == *p )
        {
            // %0 or null term means end formatting
            break;
        }
        else if ( *p < '1' || '9' < *p )
        {
            // Escaped char, skip and keep going
            ++p;
        }
        else
        {
            // Integer must be [1..99]
            size_t argPos = *p++ - '0';
            if ( '0' <= *p && *p <= '9' )
            {
                argPos *= 10;
                argPos += *p++ - '0';
            }
            assert( 0 < argPos && argPos < 100 );

            if ( argcache->size() < argPos )
            {
                // Haven't processed this arg, yet
                argcache->resize( argPos );
            }

            if ( vararg_t::Unknown == argcache->at(argPos-1).Type() )
            {
                if ( '!' != *p )
                {
                    // Assume %s as per spec
                    argcache->at(argPos-1).SetForPtr();
                }
                else
                {
                    // Step over the initial '!' and process format specification
                    ++p;

                    char ch;
                    int flags = 0;
                    int advance = 0;
                    enum CHARTYPE chclass;
                    enum STATE state = ST_PERCENT;
                    bool found_terminator = false;
                    while ( !found_terminator && ('\0' != (ch = *p++)) )
                    {
                        chclass = GetCharType( ch );
                        state = GetState( chclass, state );

                        switch ( state )
                        {
                        case ST_DOT:
                        case ST_FLAG:
                            break;

                        case ST_WIDTH:
                        case ST_PRECIS:
                            if ( '*' == ch )
                            {
                                argcache->at(argPos-1).SetForInt32();
                                ++argPos;
                                if ( argcache->size() < argPos )
                                {
                                    argcache->resize( argPos );
                                }
                            }
                            break;

                        case ST_SIZE:
                            state = ProcessSize( ch, p, &advance, &flags );
                            p += advance;
                            if ( ST_SIZE != state )
                            {
                                // Size and type flags were inconsistent
                                errno = EINVAL;
                                return false;
                            }
                            break;

                        case ST_TYPE:
                            // Group into 32-bit and 64-bit sized args
                            assert( vararg_t::Unknown == argcache->at(argPos-1).Type() );
                            switch ( ch )
                            {
                            case 'C': // chars
                            case 'c':
                                argcache->at(argPos-1).SetForInt32();
                                break;

                            case 'd': // ints
                            case 'i':
                            case 'u':
                            case 'X':
                            case 'x':
                            case 'o':
                                // INT args
                                if ( (flags & FL_I64) || (flags & FL_LONGLONG) )
                                    argcache->at(argPos-1).SetForInt64();
                                else
                                    argcache->at(argPos-1).SetForInt32();
                                break;

                            case 'S': // strings
                            case 's':
                            case 'p': // pointer
                                argcache->at(argPos-1).SetForPtr();
                                break;

                            case 'E': // doubles (not supported as per spec)
                            case 'e':
                            case 'G':
                            case 'g':
                            case 'A':
                            case 'a':
                            case 'f':
                            default:
                                errno = EINVAL;
                                return false;
                            }
                            break;

                        case ST_NORMAL:
                            if ( '!' == ch )
                            {
                                found_terminator = true;
                                break;
                            }
                            // Fall thru to error, missing terminating '!'

                        default:
                            errno = EINVAL;
                            return false;
                        }
                    }

                    if ( !found_terminator )
                    {
                        // End of string before trailing '!' was found
                        errno = EINVAL;
                        return false;
                    }
                }
            }
        }
    }

    if ( 0 < argcache->size() && NULL == Arguments )
    {
        errno = EINVAL;
        return false;
    }

    // Cache var arg values now that we know the number and sizes
    for ( std::vector< vararg_t >::iterator arg = argcache->begin(); arg != argcache->end(); ++arg )
    {
        if ( vararg_t::Unknown == arg->Type() )
        {
            // Arg not referenced in format string so assume ptr sized.
            // This is a decent assumption since every arg gets ptr-size bytes to ensure alignment
            // of later arg values.  Verified this behavior with both Windows and Linux.
            arg->SetForPtr();
        }

        vararg_t::ArgType_e argtype = arg->Type();
        assert( vararg_t::ShouldBeInt32 == argtype || vararg_t::ShouldBeInt64 == argtype );

        if ( vararg_t::ShouldBeInt32 == argtype )
        {
            arg->Int32Value( (INT)va_arg(*Arguments, INT) );
        }
        else
        {
            arg->Int64Value( (LONGLONG)va_arg(*Arguments, LONGLONG) );
        }
    }

    return true;
}

// On success, returns the number of chars written into the buffer excluding null terminator.
// On error, sets errno and returns zero.
static DWORD FormatMessageToBufferA( const char * format, char * buffer, DWORD bufferWCharSize, const std::vector< vararg_t > & args )
{
    char * msg = buffer;
    DWORD bufsize = std::min(bufferWCharSize, (DWORD)64000);
    DWORD msg_pos = 0;
    const DWORD fmtsize = 32;
    char fmt[fmtsize] = {'\0'};
    DWORD fmt_pos;
    char fmt_ch;

    const char * p = format;
    while( msg_pos < bufsize && '\0' != (fmt_ch = *p++) )
    {
        if ( '%' != fmt_ch )
        {
            msg[msg_pos++] = fmt_ch;
        }
        else if ( '0' == *p || '\0' == *p )
        {
            // %0 or null term means end formatting
            break;
        }
        else if ( *p < '1' || '9' < *p )
        {
            // Escaped char, print and keep going
            // Eg.  "%n" == '\n'
            switch ( *p )
            {
            case 'a': msg[msg_pos++] = '\a'; break;
            case 'b': msg[msg_pos++] = '\b'; break;
            case 'f': msg[msg_pos++] = '\f'; break;
            case 'n': msg[msg_pos++] = '\n'; break;
            case 'r': msg[msg_pos++] = '\r'; break;
            case 't': msg[msg_pos++] = '\t'; break;
            case 'v': msg[msg_pos++] = '\v'; break;
            default:   msg[msg_pos++] = *p;    break;
            }
            ++p;
        }
        else
        {
            // Integer must be [1..99]
            size_t argPos = *p++ - '0';
            if ( '0' <= *p && *p <= '9' )
            {
                argPos *= 10;
                argPos += *p++ - '0';
            }
            assert( 0 < argPos && argPos < 100 );

            fmt_pos = 0;
            fmt[fmt_pos++] = '%';

            if ( '!' != *p )
            {
                // Assume %s as per spec
                fmt[fmt_pos++] = 's';
                fmt[fmt_pos] = '\0';
                int chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].PtrValue() );
                if ( chars_printed < 0 )
                {
                    errno = EINVAL;
                    return 0;
                }
                msg_pos += chars_printed;
            }
            else
            {
                // Skip over '!' and build format string
                ++p;
                char ch;
                int flags = 0;
                int advance = 0;
                enum CHARTYPE chclass;
                enum STATE state = ST_PERCENT;
                bool found_terminator = false;
                while ( fmt_pos < fmtsize && !found_terminator && ('\0' != (ch = *p++)) )
                {
                    chclass = GetCharType( ch );
                    state = GetState( chclass, state );

                    switch ( state )
                    {
                    case ST_SIZE:
                        state = ProcessSize( ch, p, &advance, &flags );
                        fmt[fmt_pos++] = ch;
                        while ( fmt_pos < fmtsize && 0 < advance-- )
                        {
                            fmt[fmt_pos++] = *p++;
                        }
                        break;

                    case ST_NORMAL:
                        assert( '!' == ch );
                        found_terminator = true;
                        break;

                    case ST_INVALID:
                    case ST_PERCENT:
                        errno = EINVAL;
                        return 0;

                    default:
                        fmt[fmt_pos++] = ch;
                        break;
                    }
                }

                if ( fmtsize <= fmt_pos )
                {
                    // Should not have a format string longer than 31 chars
                    // It can happen but shouldn't (eg. a bunch of size mods like %llllllllllllllld)
                    errno = EINVAL;
                    return 0;
                }

                fmt[fmt_pos] = '\0';

                // Format string might need up to 3 args (eg. %*.*d )
                // If more than one arg, then the first ones must be 32-bit ints
                // Hence, first 64-bit arg tells us the last arg we need to send.
                int chars_printed = 0;
                if ( vararg_t::Int64 == args[argPos-1].Type() )
                {
                    chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int64Value() );
                }
                else if ( args.size() == argPos )
                {
                    // No more args so send the one Int
                    chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value() );
                }
                else if ( vararg_t::Int64 == args[argPos].Type() )
                {
                    chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos].Int64Value() );
                }
                else if ( args.size() == (argPos+1) )
                {
                    // No more args so send the two Ints
                    chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos-1].Int32Value() );
                }
                else if ( vararg_t::Int64 == args[argPos+1].Type() )
                {
                    chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos].Int32Value(), args[argPos+1].Int64Value() );
                }
                else
                {
                    chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos].Int32Value(), args[argPos+1].Int32Value() );
                }

                if ( chars_printed < 0 )
                {
                    errno = EINVAL;
                    return 0;
                }
                msg_pos += chars_printed;
            }
        }
    }

    if ( bufsize <= msg_pos )
    {
        errno = ERANGE;
        return 0;
    }

    msg[msg_pos] = '\0';
    return msg_pos;
}

// FormatMessage implementation details (see MSDN for more info)
//
// The Windows FormatMessage API is very rich, complex.  This is not an exact duplication of that function.
// Instead, the most important aspects of this function have been implemented here along with constraints to
// match how we use it within SNAC, BCP, and SQLCMD.
//
// Only these combinations of dwFlags are supported:
//  FORMAT_MESSAGE_FROM_STRING
//      Writes formatted message into supplied buffer
//  FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING
//      Allocates a buffer, writes formatted message into that buffer, returns buffer in lpBufffer
//  FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS
//      Writes fixed, English message into the supplied buffer (do not have Windows resources to get real message)
//  FORMAT_MESSAGE_FROM_HMODULE
//      SQLCMD uses this to read strings from the RLL that have not been translated to the current lang
//
// dwLanguageId is ignored for FORMAT_MESSAGE_FROM_STRING as per spec
//      For FORMAT_MESSAGE_FROM_SYSTEM, we don't have Windows resources so language is irrelevant

DWORD FormatMessageA(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPTSTR lpBuffer, DWORD nSize, va_list *Arguments)
{
    DWORD chars_printed = 0;

    // XPLAT_ODBC_TODO VSTS 718708 Localization by handling FORMAT_MESSAGE_FROM_HMODULE and dwLanguageId param
    if ( dwFlags & FORMAT_MESSAGE_FROM_STRING )
    {
        // Format specification allows for reordering of insertions relative to var arg position
        // This means we need to walk thru the format specification to find the types of the var args in var arg order
        // We extract the var args in order based on the identified types
        // Finally, we re-walk the format specfication and perform the insertions

        // First pass thru the format string to determine all args and their types
        // This first pass also validates the format string and will return an error
        // if it is invalid.  This allows FormatMessageToBuffer to have less error
        // checking.
        std::vector< vararg_t > args;
        // Based on quick scan of RC files, the largest arg count was 7 so reserve 8 slots to reduce allocations
        args.reserve(8);
        if ( GetFormatMessageArgsA( reinterpret_cast<const char *>(lpSource), &args, Arguments ) )
        {
            if ( dwFlags == (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING) )
            {
                *((char**)lpBuffer) = NULL;

                const DWORD max_size = 64000;
                char local_buf[max_size] = {'\0'};
                chars_printed = FormatMessageToBufferA( reinterpret_cast<const char *>(lpSource), local_buf, max_size, args );
                if ( 0 < chars_printed )
                {
                    size_t buf_size = std::min( max_size, std::max(nSize, (chars_printed+1)) );
                    char * return_buf = (char *)LocalAlloc(0, buf_size * sizeof(char));
                    if ( NULL == return_buf )
                    {
                        errno = ENOMEM;
                    }
                    else
                    {
                        mplat_cscpy(return_buf, local_buf);
                        *((char**)lpBuffer) = return_buf;
                    }
                }
            }
            else if ( dwFlags == FORMAT_MESSAGE_FROM_STRING )
            {
                chars_printed = FormatMessageToBufferA( reinterpret_cast<const char *>(lpSource), lpBuffer, std::min(nSize, (DWORD)64000), args );
            }
        }
    }
    else if ( dwFlags & FORMAT_MESSAGE_FROM_SYSTEM )
    {
        // Since we don't have the Windows system error messages available use a fixed message
        // Can not use a message ID for this since this same code is used by driver and tools,
        // each having their own RLL file.  Don't think we should be reserving an ID across all RLLs.
        const char systemMsg[] = "Error code 0x%X";
        if ( dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER )
        {
            *((char**)lpBuffer) = NULL;

            // Add 9 for up to 8 hex digits plus null term (ignore removal of format specs)
            const size_t msgsize = (9 + sizeof(systemMsg)/sizeof(systemMsg[0]));
            char * return_buf = (char *)LocalAlloc(0, msgsize * sizeof(char));
            if ( NULL == return_buf )
            {
                errno = ENOMEM;
            }
            else
            {
                chars_printed = mplat_snprintf_s( return_buf, msgsize, msgsize, systemMsg, dwMessageId );
                // Assert that we did our buffer size math right
                assert( chars_printed < msgsize );
                if ( 0 < chars_printed )
                {
                    *((char**)lpBuffer) = return_buf;
                }
                else
                {
                    LocalFree( return_buf );
                    errno = EINVAL;
                }
            }
        }
        else
        {
            chars_printed = mplat_snprintf_s( lpBuffer, nSize, nSize, systemMsg, dwMessageId );
        }
    }

    return chars_printed;
}


//--------Other definitions from xplat stub sources--------------

BOOL IsDBCSLeadByte(__inn BYTE  TestChar)
{
    // XPLAT_ODBC_TODO: This is to allow BatchParser to function
    // BatchParser will single step thru utf8 code points
    // BatchParser needs to become utf8-aware
    // VSTS 718708 Localization
    if ( CP_UTF8 == SystemLocale::Singleton().AnsiCP() )
        return FALSE;
    // XPLAT_ODBC_TODO

    return IsDBCSLeadByteEx(SystemLocale::Singleton().AnsiCP(), TestChar);
}

BOOL IsDBCSLeadByteEx(
    __inn UINT  CodePage,
    __inn BYTE  TestChar)
{
    if ( 1 == SystemLocale::MaxCharCchSize(CodePage) )
        return FALSE;

    // Lead byte ranges for code pages, inclusive:
    // CP932
    //      0x81-0x9f, 0xe0-0xfc
    // CP936, CP949, CP950
    //      0x81-0xfe
    assert( 932 == CodePage || 936 == CodePage || 949 == CodePage || 950 == CodePage );
    if ( 932 == CodePage )
    {
        if ( TestChar < (unsigned char)0x81
            || (unsigned char)0xfc < TestChar
            || ((unsigned char)0x9f < TestChar && TestChar < (unsigned char)0xe0) )
        {
            return FALSE;
        }
    }
    else if ( TestChar < (unsigned char)0x81 || TestChar == (unsigned char)0xff )
        return FALSE;

    return TRUE;
}

int mplat_vsnprintf( char * buffer, size_t count, const char * format, va_list args )
{
    BufferOutput<char> output( buffer, count );
    return FormattedPrintA( &output, format, args );
}

int mplat_snprintf_s( char *buffer, size_t bufsize, size_t count, const char *format, ... )
{
    va_list args;
    va_start( args, format );
    int retcode = mplat_vsnprintf( buffer, std::min(bufsize, count), format, args );
    va_end( args );
    return retcode;
}

char * mplat_cscpy( char * dst, const char * src )
{
    char * cp = dst;

    while( (*cp++ = *src++) )
        ;       /* Copy src over dst */

    return( dst );
}

size_t mplat_wcslen( const WCHAR * str )
{
    const WCHAR * eos = str;
    while( *eos++ )
    {
    }
    return( (size_t)(eos - str- 1) );
}

HLOCAL LocalAlloc(UINT uFlags, SIZE_T uBytes)
{
    assert(uFlags == 0); // For now
    return malloc(uBytes);
}

HLOCAL LocalFree(HLOCAL hMem)
{
    assert(hMem != NULL);

    free(hMem);
    return NULL;
}
