| /* ********************************************************** |
| * Copyright (c) 2011-2014 Google, Inc. All rights reserved. |
| * Copyright (c) 2002-2010 VMware, Inc. All rights reserved. |
| * **********************************************************/ |
| |
| /* |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * * Neither the name of VMware, Inc. nor the names of its contributors may be |
| * used to endorse or promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| */ |
| |
| /* Copyright (c) 2003-2007 Determina Corp. */ |
| /* Copyright (c) 2002-2003 Massachusetts Institute of Technology */ |
| |
| /* |
| * iox.h: i/o routines for both Linux and Windows |
| */ |
| |
| #include <limits.h> /* for UCHAR_MAX */ |
| |
| #ifdef IOX_WIDE_CHAR |
| # define TCHAR wchar_t |
| # define _T(s) L##s |
| # define TNAME(n) n##_wide |
| # define IF_WIDE_ELSE(x,y) x |
| #else |
| # define TCHAR char |
| # define _T(s) s |
| # define TNAME(n) n |
| # define IF_WIDE_ELSE(x,y) y |
| #endif |
| |
| const static char TNAME(base_letters)[] = { |
| _T('0'),_T('1'),_T('2'),_T('3'),_T('4'),_T('5'),_T('6'),_T('7'), |
| _T('8'),_T('9'),_T('a'),_T('b'),_T('c'),_T('d'),_T('e'),_T('f') |
| }; |
| const static char TNAME(base_letters_cap)[] = { |
| _T('0'),_T('1'),_T('2'),_T('3'),_T('4'),_T('5'),_T('6'),_T('7'), |
| _T('8'),_T('9'),_T('A'),_T('B'),_T('C'),_T('D'),_T('E'),_T('F') |
| }; |
| |
| /* convert uint64 to a string */ |
| static TCHAR * |
| TNAME(uint64_to_str)(uint64 num, int base, TCHAR * buf, int decimal, bool caps) |
| { |
| int cnt; |
| TCHAR *p = buf; |
| int end = (43 > decimal ? 43 : decimal); |
| ASSERT(decimal < BUF_SIZE - 1); /* so don't overflow buf */ |
| |
| buf[end] = '\0'; |
| for (cnt = end - 1; cnt >= 0; cnt--) { |
| buf[cnt] = caps ? TNAME(base_letters_cap)[(num %base)] : |
| TNAME(base_letters)[(num % base)]; |
| num /= base; |
| } |
| |
| while (*p && *p == _T('0') && end - decimal > 0) { |
| p++; |
| decimal++; |
| } |
| |
| return p; |
| } |
| |
| /* convert ulong to a string */ |
| static TCHAR * |
| TNAME(ulong_to_str)(ulong num, int base, TCHAR *buf, int decimal, bool caps) |
| { |
| int cnt; |
| TCHAR *p = buf; |
| int end = (22 > decimal ? 22 : decimal); /* room for 64 bits octal */ |
| ASSERT(decimal < BUF_SIZE - 1); /* so don't overflow buf */ |
| |
| buf[end] = '\0'; |
| for (cnt = end - 1; cnt >= 0; cnt--) { |
| buf[cnt] = caps ? TNAME(base_letters_cap)[(num %base)] : |
| TNAME(base_letters)[(num % base)]; |
| num /= base; |
| } |
| |
| while (*p && *p == _T('0') && end - decimal> 0) { |
| p++; |
| decimal++; |
| } |
| |
| return p; |
| } |
| |
| /* N.B.: when building with /QIfist casting rounds instead of truncating (i#763)! |
| * Thus, use double2int_trunc() instead of casting. |
| */ |
| static TCHAR * |
| TNAME(double_to_str)(double d, int decimal, TCHAR *buf, bool force_dot, |
| bool suppress_zeros) |
| { |
| /* support really big numbers with %f? */ |
| TCHAR tmpbuf[BUF_SIZE]; |
| TCHAR *pre, *post, *c; |
| long predot, postdot, sub, i; |
| |
| /* get pre and post dot sections as integers */ |
| if (d < 0) |
| d = -d; |
| if (decimal > 0) |
| predot = double2int_trunc(d); |
| else |
| predot = double2int(d); |
| sub = 1; |
| for (i=0; i<decimal; i++) |
| sub *= 10; |
| postdot = double2int((d - double2int_trunc(d)) * (double)sub); |
| if (postdot == sub) { |
| /* we had a .9* that rounded up! */ |
| postdot = 0; |
| predot++; |
| } |
| |
| pre = TNAME(ulong_to_str)((ulong)predot, 10, tmpbuf, 1, false); |
| for (i=0, c = pre; *c; c++) |
| buf[i++] = *c; |
| if (force_dot || !(decimal == 0 || (suppress_zeros && postdot == 0))) { |
| buf[i++] = _T('.'); |
| post = TNAME(ulong_to_str)((ulong)postdot, 10, tmpbuf, decimal, false); |
| for (c = post; *c; c++) |
| buf[i++] = *c; |
| /* remove trailing zeros */ |
| if (suppress_zeros) { |
| while (buf[i-1] == _T('0')) |
| i--; |
| } |
| } |
| |
| buf[i] = '\0'; |
| ASSERT(i<BUF_SIZE); /* make sure don't overflow buffer */ |
| return buf; |
| } |
| |
| static TCHAR * |
| TNAME(double_to_exp_str)(double d, int exp, int decimal, TCHAR * buf, |
| bool force_dot, bool suppress_zeros, bool caps) |
| { |
| TCHAR tmp_buf[BUF_SIZE]; |
| TCHAR *tc; |
| int i = 0; |
| uint abval; |
| |
| tc = TNAME(double_to_str)(d, decimal, tmp_buf, force_dot, suppress_zeros); |
| while (*tc) { |
| buf[i++] = *tc++; |
| } |
| if (caps) |
| buf[i++] = _T('E'); |
| else |
| buf[i++] = _T('e'); |
| if (exp < 0) { |
| buf[i++] = _T('-'); |
| abval = -exp; |
| } else { |
| buf[i++] = _T('+'); |
| abval = exp; |
| } |
| /* exp value always printed as at least 2 characters */ |
| tc = TNAME(ulong_to_str)((ulong)abval, 10, tmp_buf, 2, false); |
| while (*tc) { |
| buf[i++] = *tc++; |
| } |
| buf[i] = '\0'; |
| ASSERT(i < BUF_SIZE); /* make sure don't overflow buffer */ |
| return buf; |
| } |
| |
| /* i#386: separated out to avoid floating-point instrs in our_vsnprintf */ |
| static const TCHAR * |
| TNAME(our_vsnprintf_float)(double val, const TCHAR *c, TCHAR prefixbuf[3], |
| TCHAR buf[BUF_SIZE], int decimal, bool space_flag, |
| bool plus_flag, bool pound_flag) |
| { |
| const TCHAR *str; |
| bool caps = (*c == _T('E')) || (*c == _T('G')); |
| double d = val; |
| int exp = 0; |
| bool is_g = (*c == _T('g') || *c == _T('G')); |
| /* i#1213: we must mask all fpu exceptions prior to running this code, |
| * as it assumes div-by-zero won't raise an exception. |
| * The caller must have already saved the app's full fpu state. |
| */ |
| dr_fpu_exception_init(); |
| /* check for NaN */ |
| if (val != val) { |
| if (caps) |
| str = _T("NAN"); |
| else |
| str = _T("nan"); |
| if (space_flag) |
| prefixbuf[0] = _T(' '); |
| return str; |
| } |
| if (decimal == -1) |
| decimal = 6; /* default */ |
| if (val >= 0 && space_flag) |
| prefixbuf[0] = _T(' '); /* get prefix */ |
| if (val >= 0 && plus_flag) |
| prefixbuf[0] = _T('+'); |
| if (val < 0) |
| prefixbuf[0] = _T('-'); |
| /* check for inf */ |
| if (val == pos_inf || val == neg_inf) { |
| if (caps) |
| str = _T("INF"); |
| else |
| str = _T("inf"); |
| return str; |
| } |
| if (*c == _T('f')) { /* ready to generate string now for f */ |
| str = TNAME(double_to_str)(val, decimal, buf, pound_flag, false); |
| return str; |
| } |
| /* get exponent value */ |
| while (d >= 10.0 || d <= -10.0) { |
| exp++; |
| d = d / 10.0; |
| } |
| while (d < 1.0 && d > -1.0 && d != 0.0) { |
| exp--; |
| d = d * 10.0; |
| } |
| |
| if (is_g) |
| decimal--; /* g/G precision is number of signifigant digits */ |
| if (is_g && exp >= -4 && exp <= decimal) { |
| /* exp is small enough for f, print without exponent */ |
| str = TNAME(double_to_str)(val, decimal, buf, pound_flag, !pound_flag); |
| } else { |
| /* print with exponent */ |
| str = TNAME(double_to_exp_str)(d, exp, decimal, buf, pound_flag, |
| is_g && !pound_flag, caps); |
| } |
| return str; |
| } |
| |
| /* Returns number of chars printed, not including the null terminator. |
| * If number is larger than max, |
| * prints max (without null) and returns -1. |
| * For %S on Windows, converts between UTF-8 and UTF-16, and returns -1 |
| * if passed an invalid encoding. |
| * (Thus, matches Windows snprintf, not Linux.) |
| */ |
| int |
| TNAME(our_vsnprintf)(TCHAR *s, size_t max, const TCHAR *fmt, va_list ap) |
| { |
| const TCHAR *c; |
| const TCHAR *str = NULL; |
| TCHAR *start = s; |
| TCHAR buf[BUF_SIZE]; |
| |
| if (fmt == NULL) |
| return 0; |
| if (max == 0) |
| goto max_reached; |
| |
| c = fmt; |
| while (*c) { |
| if (*c == _T('%')) { |
| int fill = 0; |
| TCHAR filler = _T(' '); |
| int decimal = -1; /* codes defaults (6 int, 1 float, all string) */ |
| TCHAR charbuf[2] = {_T('\0'),_T('\0')}; |
| TCHAR prefixbuf[3] = {_T('\0'),_T('\0'),_T('\0')}; |
| TCHAR *prefix; |
| bool minus_flag = false; |
| bool plus_flag = false; |
| bool pound_flag = false; |
| bool space_flag = false; |
| bool h_type = false; |
| bool l_type = false; |
| bool ll_type = false; |
| #ifdef IOX_WIDE_CHAR |
| char *wstr = NULL; |
| #else |
| wchar_t *wstr = NULL; |
| #endif |
| prefix = prefixbuf; |
| c++; |
| ASSERT(*c); |
| |
| /* Collect flags -, +, #, 0, */ |
| while (*c == _T('0') || *c == _T('-') || *c == _T('#') || *c == _T('+') || |
| *c == _T(' ')) { |
| if (*c == _T('0')) |
| filler = _T('0'); |
| if (*c == _T('-')) |
| minus_flag = true; |
| if (*c == _T('+')) |
| plus_flag = true; |
| if (*c == _T('#')) |
| pound_flag = true; |
| if (*c == _T(' ')) |
| space_flag = true; |
| c++; |
| ASSERT(*c); |
| } |
| if (minus_flag) |
| filler = _T(' '); |
| if (plus_flag) |
| space_flag = false; |
| |
| /* get field width */ |
| if (*c == _T('*')) { |
| fill = va_arg(ap, int); |
| if (fill < 0) { |
| minus_flag = true; |
| fill = -fill; |
| } |
| c++; |
| ASSERT(*c); |
| } else { |
| while (*c >= _T('0') && *c <= _T('9')) { |
| fill *= 10; |
| fill += *c - _T('0'); |
| c++; |
| ASSERT(*c); |
| } |
| } |
| |
| /* get precision */ |
| if (*c == _T('.')) { |
| c++; |
| ASSERT(*c); |
| decimal = 0; |
| if (*c == _T('*')) { |
| decimal = va_arg(ap, int); |
| c++; |
| ASSERT(*c); |
| } else { |
| while (*c >= _T('0') && *c <= _T('9')) { |
| decimal *= 10; |
| decimal += *c - _T('0'); |
| c++; |
| ASSERT(*c); |
| } |
| } |
| } |
| |
| /* get size modifiers l, h, ll/L */ |
| if (*c == _T('l') || *c == _T('L') || *c == _T('h')) { |
| if (*c == _T('L')) |
| ll_type = true; |
| if (*c == _T('h')) |
| h_type = true; |
| if (*c == _T('l')) { |
| c++; |
| ASSERT(*c); |
| if (*c == _T('l')) { |
| ll_type = true; |
| c++; |
| ASSERT(*c); |
| } else { |
| l_type = true; |
| } |
| } else { |
| c++; |
| ASSERT(*c); |
| } |
| } else if (*c == _T('I')) { /* %I64 or %I32, to match Win32 */ |
| if (*(c+1)==_T('6') && *(c+2)==_T('4')) { |
| ll_type = true; |
| c += 3; |
| ASSERT(*c); |
| } else if (*(c+1)==_T('3') && *(c+2)==_T('2')) { |
| l_type = true; |
| c += 3; |
| ASSERT(*c); |
| } else |
| ASSERT(false && "unsupported printf code"); |
| } |
| |
| /* dispatch */ |
| switch (*c) { |
| case _T('%'): |
| charbuf[0] = _T('%'); |
| str = charbuf; |
| break; |
| case _T('d'): |
| case _T('i'): |
| { |
| long val; |
| ulong abval = 0; |
| int64 val64; |
| uint64 abval64 = 0; |
| bool negative = false; |
| if (decimal == -1) |
| decimal = 1; /* defaults */ |
| else |
| filler = _T(' '); |
| if (ll_type) { |
| val64 = va_arg(ap, int64); /* get arg */ |
| negative = (val64 < 0); |
| if (negative) |
| abval64= -val64; |
| else |
| abval64 = val64; |
| } else { |
| if (l_type) |
| val = va_arg(ap, long); /* get arg */ |
| else if (h_type) |
| val = (long)va_arg(ap, int); /* short is promoted to int */ |
| else |
| val = (long)va_arg(ap, int); |
| negative = (val < 0); |
| if (negative) |
| abval = -val; |
| else |
| abval = val; |
| } |
| if (!negative && space_flag) |
| prefixbuf[0] = _T(' '); /* set prefix */ |
| if (!negative && plus_flag) |
| prefixbuf[0] = _T('+'); |
| if (negative) |
| prefixbuf[0] = _T('-'); |
| /* generate string */ |
| if (ll_type) |
| str = TNAME(uint64_to_str)(abval64, 10, buf, decimal, false); |
| else |
| str = TNAME(ulong_to_str)(abval, 10, buf, decimal, false); |
| break; |
| } |
| case _T('u'): |
| /* handle long long u type */ |
| if (decimal == -1) |
| decimal = 1; |
| else |
| filler = _T(' '); |
| if (ll_type) { |
| str = TNAME(uint64_to_str)((uint64)va_arg(ap, uint64), 10, buf, |
| decimal, false); |
| break; |
| } |
| /* note no break */ |
| case _T('x'): |
| case _T('X'): |
| case _T('o'): |
| case _T('p'): |
| { |
| ptr_uint_t val; |
| bool caps = *c == _T('X'); |
| int base = 10; |
| if (decimal == -1) |
| decimal = 1; /* defaults */ |
| else |
| filler = _T(' '); |
| if (*c == _T('p')) |
| decimal = 2 * sizeof(void *); /* pointer precision */ |
| /* generate prefix */ |
| if ((pound_flag && *c != _T('u')) || (*c == _T('p'))) { |
| prefixbuf[0] = _T('0'); |
| if (*c == _T('x') || *c == _T('p')) |
| prefixbuf[1] = _T('x'); |
| if (*c == _T('X')) |
| prefixbuf[1] = _T('X'); |
| } |
| if (*c == _T('o')) |
| base = 8; /* determine base */ |
| if (*c == _T('x') || *c == _T('X') || *c == _T('p')) |
| base = 16; |
| ASSERT(sizeof(void *) == sizeof(val)); |
| if (*c == _T('p')) { |
| val = (ptr_uint_t) va_arg(ap, void *); /* get val */ |
| #ifdef X64 |
| str = TNAME(uint64_to_str)((uint64)val, base, buf, decimal, caps); |
| break; |
| #endif |
| } else if (l_type) |
| val = (ptr_uint_t) va_arg(ap, ulong); |
| else if (h_type) |
| val = (ptr_uint_t) va_arg(ap, uint); /* ushort promoted */ |
| else if (ll_type) { |
| str = TNAME(uint64_to_str)((uint64)va_arg(ap, uint64), base, buf, |
| decimal, caps); |
| break; |
| } else |
| val = (ptr_uint_t) va_arg(ap, uint); |
| /* generate string */ |
| ASSERT(sizeof(val) >= sizeof(ulong)); |
| str = TNAME(ulong_to_str)((ulong)val, base, buf, decimal, caps); |
| break; |
| } |
| case _T('c'): |
| /* FIXME: using int instead of char seems to work for RH7.2 as |
| * well as 8.0, but using char crashes 8.0 but not 7.2 |
| */ |
| #ifdef VA_ARG_CHAR2INT |
| charbuf[0] = (TCHAR) va_arg(ap, int); /* char -> int in va_list */ |
| #else |
| charbuf[0] = va_arg(ap, TCHAR); |
| #endif |
| str = charbuf; |
| break; |
| case _T('s'): |
| if (!IF_WIDE_ELSE(h_type,l_type)) { |
| str = va_arg(ap, TCHAR*); |
| break; |
| } |
| /* fall-through */ |
| case _T('S'): |
| #ifdef IOX_WIDE_CHAR |
| h_type = true; |
| wstr = va_arg(ap, char*); |
| #else |
| l_type = true; |
| wstr = va_arg(ap, wchar_t*); |
| #endif |
| break; |
| case _T('g'): |
| case _T('G'): |
| if (decimal == 0 || decimal == -1) |
| decimal = 1; /* default */ |
| /* no break */ |
| case _T('e'): |
| case _T('E'): |
| case _T('f'): |
| { |
| /* pretty sure will always be promoted to a double in arg list */ |
| double val = va_arg(ap, double); |
| str = TNAME(our_vsnprintf_float)(val, c, prefixbuf, buf, decimal, |
| space_flag, plus_flag, pound_flag); |
| break; |
| } |
| case _T('n'): |
| { |
| /* save num of chars printed so far in address specified */ |
| uint num_char = (uint) (s - start); |
| /* yes, snprintf on all platforms returns int, not ssize_t */ |
| IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_int(s - start))); |
| if (l_type) { |
| long * val = va_arg(ap, long *); |
| *val = (long) num_char; |
| } else if (h_type) { |
| short * val = va_arg(ap, short *); |
| *val = (short) num_char; |
| } else { |
| int * val = va_arg(ap, int *); |
| *val = num_char; |
| } |
| buf[0] = '\0'; |
| str = buf; |
| break; |
| } |
| /* FIXME : support the following? */ |
| case _T('a'): |
| case _T('A'): |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| /* if filler is 0 fill after prefix, else fill before prefix */ |
| /* if - flag then fill after str and ignore filler type */ |
| |
| /* calculate number of fill characters */ |
| if (fill > 0) { |
| size_t plen = IF_WIDE_ELSE(wcslen,strlen)(prefix); |
| if (wstr != NULL) { |
| /* XXX: this doesn't take into account UTF-16 or UTF-8 |
| * multi-byte chars. For now we just don't support |
| * properly filling those. It should only matter |
| * for pretty-printing. |
| */ |
| size_t wlen = IF_WIDE_ELSE(strlen,wcslen)(wstr); |
| IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_uint(wlen + plen))); |
| fill -= (uint) (wlen + plen); |
| } else { |
| size_t len = IF_WIDE_ELSE(wcslen,strlen)(str); |
| IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_uint(len + plen))); |
| fill -= (uint) (len + plen); |
| } |
| } |
| /* insert prefix if filler is 0, filler won't be 0 if - flag is set */ |
| if (filler == _T('0')) { |
| while (*prefix) { |
| if ((size_t)(s - start) >= max) |
| goto max_reached; |
| *s = *prefix; |
| s++; |
| prefix++; |
| } |
| } |
| /* fill now if not left justified */ |
| if (fill > 0 && !minus_flag) { |
| int i; |
| for (i = 0; i < fill; i++) { |
| if ((size_t)(s - start) >= max) |
| goto max_reached; |
| *s = filler; |
| s++; |
| } |
| } |
| /* insert prefix if not 0 filling */ |
| if (filler != _T('0')) { |
| while (*prefix) { |
| if ((size_t)(s - start) >= max) |
| goto max_reached; |
| *s = *prefix; |
| s++; |
| prefix++; |
| } |
| } |
| /* insert the actual str representation */ |
| if (wstr != NULL) { |
| #ifdef WINDOWS |
| /* We follow Linux sprintf which has precision on multi-byte |
| * transformation as specifying bytes, not unicode chars. |
| * MSDN docs say "characters", but Windows %S doesn't support |
| * any conversion other than truncating to ascii (or 0 if not |
| * ascii). |
| */ |
| ssize_t els; |
| size_t max_bytes = max - (s - start); |
| /* string precision */ |
| if ((*c == _T('s') || *c == _T('S')) && decimal >= 0 && |
| (size_t)decimal < max_bytes) |
| max_bytes = decimal; |
| els = IF_WIDE_ELSE(utf8_to_utf16, utf16_to_utf8) |
| (s, max_bytes, wstr, 0, NULL); |
| if (els < 0) |
| return -1; |
| s += els; |
| if ((size_t)(s - start) >= max) |
| goto max_reached; |
| #else |
| while (*wstr) { |
| if ((size_t)(s - start) >= max) |
| goto max_reached; |
| if ((*c == _T('s') || *c == _T('S')) && decimal == 0) |
| break; /* check string precision */ |
| decimal--; |
| /* we only support ascii */ |
| ASSERT((unsigned short)(*wstr) <= UCHAR_MAX); |
| *s = (TCHAR) *wstr; |
| s++; |
| wstr++; |
| } |
| #endif |
| } else { |
| if (str == NULL) |
| str = _T("<NULL>"); |
| while (*str) { |
| if ((size_t)(s - start) >= max) |
| goto max_reached; |
| if (*c == _T('s') && decimal == 0) |
| break; /* check string precision */ |
| decimal--; |
| *s = *str; |
| s++; |
| str++; |
| } |
| } |
| /* if left justified do the fill now after the actual string */ |
| if (fill > 0 && minus_flag) { |
| int i; |
| for (i = 0; i < fill; i++) { |
| if ((size_t)(s - start) >= max) |
| goto max_reached; |
| *s = filler; |
| s++; |
| } |
| } |
| c++; |
| } |
| else { |
| const TCHAR *cstart = c; |
| int nbytes = 0; |
| while (*c && *c != _T('%')) { |
| nbytes++; |
| c++; |
| } |
| while (cstart < c) { |
| if ((size_t)(s - start) >= max) |
| goto max_reached; |
| *s = *cstart; |
| s++; |
| cstart++; |
| } |
| } |
| } |
| |
| if (max == 0 || (size_t)(s - start) < max) |
| *s = '\0'; |
| |
| /* yes, snprintf on all platforms returns int, not ssize_t */ |
| IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_int(s - start))); |
| return (int) (s - start); |
| |
| max_reached: |
| return -1; |
| } |
| |
| /* Returns number of chars printed. If number is larger than max, |
| * prints max (without null) and returns -1. |
| * (Thus, matches Windows snprintf, not Linux.) |
| */ |
| int |
| TNAME(our_snprintf)(TCHAR *s, size_t max, const TCHAR *fmt, ...) |
| { |
| int res; |
| va_list ap; |
| ASSERT(s); |
| va_start(ap, fmt); |
| res = TNAME(our_vsnprintf)(s, max, fmt, ap); |
| va_end(ap); |
| return res; |
| } |
| |
| #undef TCHAR |
| #undef _T |
| #undef TNAME |
| #undef IF_WIDE_ELSE |