1588 lines
55 KiB
C++
1588 lines
55 KiB
C++
|
|
//-----------------------------------------------------------------------------
|
|
// File: FormattedPrint.cpp
|
|
//
|
|
//
|
|
// Contents: Contains functions for handling Windows format strings
|
|
// and UTF-16 on non-Windows platforms
|
|
//
|
|
// Microsoft Drivers 5.1 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];
|
|
|
|
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;
|
|
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)strlen(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];
|
|
|
|
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];
|
|
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];
|
|
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;
|
|
}
|