/** * Copyright (2017) Baidu Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * File: lightduer_snprintf.c * Auth: Leliang Zhang(zhangleliang@baidu.com) * Desc: Provide snprintf/vsnprintf implementation, * because esp32 platform, sprintf need too much stack, * so these codes here, try to avoid the huge stack-implementation. * reference: */ #ifdef ENABLE_LIGHTDUER_SNPRINTF #include "lightduer_snprintf.h" #include #include #include #ifdef isdigit #undef isdigit #endif #define isdigit(c) ((c) >= '0' && (c) <= '9') #define ZEROPAD 1 /* pad with zero */ #define SIGN 2 /* unsigned/signed long */ #define PLUS 4 /* show plus */ #define SPACE 8 /* space if plus */ #define LEFT 16 /* left justified */ #define SMALL 32 /* Must be 32 == 0x20 */ #define SPECIAL 64 /* 0x */ /* For copying strings longer or equal to 'breakeven_point' * it is more efficient to call memcpy() than to do it inline. * The value depends mostly on the processor architecture, * but also on the compiler and its optimization capabilities. * The value is not critical, some small value greater than zero * will be just fine if you don't care to squeeze every drop * of performance out of the code. * * Small values favor memcpy, large values favor inline code. */ #if defined(__i386__) || defined(__i386) #define breakeven_point 12 /* Intel Pentium/Linux - gcc 2.96 */ #endif #ifndef breakeven_point #define breakeven_point 6 /* some reasonable one-size-fits-all value */ #endif #define fast_memcpy(d,s,n) \ {\ register size_t nn = (size_t)(n);\ if (nn >= breakeven_point) {\ memcpy((d), (s), nn);\ } else if (nn > 0) { /* proc call overhead is worth only for large strings*/\ register char *dd = NULL;\ register const char *ss = NULL;\ for (ss = (s), dd = (d); nn > 0; nn--) {\ *dd++ = *ss++;\ }\ }\ } #define fast_memset(d,c,n) \ {\ register size_t nn = (size_t)(n);\ if (nn >= breakeven_point) {\ memset((d), (int)(c), nn);\ } else if (nn > 0) { /* proc call overhead is worth only for large strings*/\ register char *dd = NULL;\ register const int cc = (int)(c);\ for (dd = (d); nn > 0; nn--) {\ *dd++ = cc;\ }\ }\ } #define __do_div(n, base) ({ \ int __res; \ __res = ((unsigned long) n) % (unsigned) base; \ n = ((unsigned long) n) / (unsigned) base; \ __res; }) static int skip_atoi(const char **s) { int i = 0; while (isdigit(**s)) { i = i * 10 + *((*s)++) - '0'; } return i; } static char *number(char *str, long num, int base, int size, int precision, int type) { /* we are called with base 8, 10 or 16, only, thus don't need "G..." */ static const char digits[16] = "0123456789ABCDEF"; /* "GHIJKLMNOPQRSTUVWXYZ"; */ char tmp[66]; char c = 0; char sign = 0; char locase = 0; int i = 0; /* locase = 0 or 0x20. ORing digits or letters with 'locase' * produces same digits or (maybe lowercased) letters */ locase = (type & SMALL); if (type & LEFT) { type &= ~ZEROPAD; } if (base < 2 || base > 16) { return NULL; } c = (type & ZEROPAD) ? '0' : ' '; sign = 0; if (type & SIGN) { if (num < 0) { sign = '-'; num = -num; size--; } else if (type & PLUS) { sign = '+'; size--; } else if (type & SPACE) { sign = ' '; size--; } } if (type & SPECIAL) { if (base == 16) { size -= 2; } else if (base == 8) { size--; } } i = 0; if (num == 0) { tmp[i++] = '0'; } else while (num != 0) { tmp[i++] = (digits[__do_div(num, base)] | locase); } if (i > precision) { precision = i; } size -= precision; if (!(type & (ZEROPAD + LEFT))) while (size-- > 0) { *str++ = ' '; } if (sign) { *str++ = sign; } if (type & SPECIAL) { if (base == 8) { *str++ = '0'; } else if (base == 16) { *str++ = '0'; *str++ = ('X' | locase); } } if (!(type & LEFT)) while (size-- > 0) { *str++ = c; } while (i < precision--) { *str++ = '0'; } while (i-- > 0) { *str++ = tmp[i]; } while (size-- > 0) { *str++ = ' '; } return str; } static int duer_vsprintf(char *buf, const char *fmt, va_list args) { int len = 0; unsigned long num = 0; int i = 0; int base = 0; char *str = NULL; const char *s = NULL; int flags = 0; /* flags to number() */ int field_width = 0; /* width of output field */ int precision = 0; /* min. # of digits for integers; max number of chars for from string */ int qualifier = 0; /* 'h', 'l', or 'L' for integer fields */ for (str = buf; *fmt; ++fmt) { if (*fmt != '%') { *str++ = *fmt; continue; } /* process flags */ flags = 0; repeat: ++fmt; /* this also skips first '%' */ switch (*fmt) { case '-': flags |= LEFT; goto repeat; case '+': flags |= PLUS; goto repeat; case ' ': flags |= SPACE; goto repeat; case '#': flags |= SPECIAL; goto repeat; case '0': flags |= ZEROPAD; goto repeat; } /* get field width */ field_width = -1; if (isdigit(*fmt)) { field_width = skip_atoi(&fmt); } else if (*fmt == '*') { ++fmt; /* it's the next argument */ field_width = va_arg(args, int); if (field_width < 0) { field_width = -field_width; flags |= LEFT; } } /* get the precision */ precision = -1; if (*fmt == '.') { ++fmt; if (isdigit(*fmt)) { precision = skip_atoi(&fmt); } else if (*fmt == '*') { ++fmt; /* it's the next argument */ precision = va_arg(args, int); } if (precision < 0) { precision = 0; } } /* get the conversion qualifier */ qualifier = -1; if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') { qualifier = *fmt; ++fmt; } /* default base */ base = 10; switch (*fmt) { case 'c': if (!(flags & LEFT)) while (--field_width > 0) { *str++ = ' '; } *str++ = (unsigned char)va_arg(args, int); while (--field_width > 0) { *str++ = ' '; } continue; case 's': s = va_arg(args, char *); len = strnlen(s, precision); if (!(flags & LEFT)) while (len < field_width--) { *str++ = ' '; } for (i = 0; i < len; ++i) { *str++ = *s++; } while (len < field_width--) { *str++ = ' '; } continue; case 'p': if (field_width == -1) { field_width = 2 * sizeof(void *); flags |= ZEROPAD; } str = number(str, (unsigned long)va_arg(args, void *), 16, field_width, precision, flags); continue; case 'n': if (qualifier == 'l') { long *ip = va_arg(args, long *); *ip = (str - buf); } else { int *ip = va_arg(args, int *); *ip = (str - buf); } continue; case '%': *str++ = '%'; continue; /* integer number formats - set up the flags and "break" */ case 'o': base = 8; break; case 'x': flags |= SMALL; case 'X': base = 16; break; case 'd': case 'i': flags |= SIGN; case 'u': break; default: *str++ = '%'; if (*fmt) { *str++ = *fmt; } else { --fmt; } continue; } if (qualifier == 'l') { num = va_arg(args, unsigned long); } else if (qualifier == 'h') { num = (unsigned short)va_arg(args, int); if (flags & SIGN) { num = (short)num; } } else if (flags & SIGN) { num = va_arg(args, int); } else { num = va_arg(args, unsigned int); } str = number(str, num, base, field_width, precision, flags); } *str = '\0'; return str - buf; } int duer_sprintf(char *buf, const char *fmt, ...) { va_list args; int i = 0; va_start(args, fmt); i = duer_vsprintf(buf, fmt, args); va_end(args); return i; } int lightduer_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap) { size_t str_l = 0; const char *p = fmt; /* In contrast with POSIX, the ISO C99 now says * that str can be NULL and str_m can be 0. * This is more useful than the old: if (str_m < 1) return -1; */ if (!p) { p = ""; } while (*p) { if (*p != '%') { // if (str_l < str_m) str[str_l++] = *p++; -- this would be sufficient // but the following code achieves better performance for cases // where format string is long and contains few conversions const char *q = strchr(p + 1, '%'); size_t n = !q ? strlen(p) : (q - p); if (str_l < str_m) { size_t avail = str_m - str_l; fast_memcpy(str + str_l, p, (n > avail ? avail : n)); } p += n; str_l += n; } else { const char *starting_p = NULL; size_t min_field_width = 0; size_t precision = 0; int zero_padding = 0; int precision_specified = 0; int justify_left = 0; int alternate_form = 0; int force_sign = 0; int space_for_positive = 1; /* If both the ' ' and '+' flags appear, the ' ' flag should be ignored. */ char length_modifier = '\0'; /* allowed values: \0, h, l, L */ char tmp[32];/* temporary buffer for simple numeric->string conversion */ const char *str_arg = NULL; /* string address in case of string argument */ size_t str_arg_l = 0; /* natural field width of arg without padding and sign */ unsigned char uchar_arg = 0; /* unsigned char argument value - only defined for c conversion. N.B. standard explicitly states the char argument for the c conversion is unsigned */ size_t number_of_zeros_to_pad = 0; /* number of zeros to be inserted for numeric conversions as required by the precision or minimal field width */ size_t zero_padding_insertion_ind = 0; /* index into tmp where zero padding is to be inserted */ char fmt_spec = '\0'; /* current conversion specifier character */ str_arg = NULL; starting_p = p; p++; /* skip '%' */ /* parse flags */ while (*p == '0' || *p == '-' || *p == '+' || *p == ' ' || *p == '#' || *p == '\'') { switch (*p) { case '0': zero_padding = 1; break; case '-': justify_left = 1; break; case '+': force_sign = 1; space_for_positive = 0; break; case ' ': force_sign = 1; /* If both the ' ' and '+' flags appear, the ' ' flag should be ignored */ #ifdef PERL_COMPATIBLE /* ... but in Perl the last of ' ' and '+' applies */ space_for_positive = 1; #endif break; case '#': alternate_form = 1; break; case '\'': break; } p++; } /* If the '0' and '-' flags both appear, the '0' flag should be ignored. */ /* parse field width */ if (*p == '*') { int j = va_arg(ap, int); p++; if (j >= 0) { min_field_width = j; } else { min_field_width = -j; justify_left = 1; } } else if (isdigit((int)(*p))) { /* size_t could be wider than unsigned int; make sure we treat argument like common implementations do */ unsigned int uj = *p++ - '0'; while (isdigit((int)(*p))) { uj = 10 * uj + (unsigned int)(*p++ - '0'); } min_field_width = uj; } /* parse precision */ if (*p == '.') { p++; precision_specified = 1; if (*p == '*') { int j = va_arg(ap, int); p++; if (j >= 0) { precision = j; } else { precision_specified = 0; precision = 0; /* NOTE: * Solaris 2.6 man page claims that in this case the precision * should be set to 0. Digital Unix 4.0, HPUX 10 and BSD man page * claim that this case should be treated as unspecified precision, * which is what we do here. */ } } else if (isdigit((int)(*p))) { /* size_t could be wider than unsigned int; make sure we treat argument like common implementations do */ unsigned int uj = *p++ - '0'; while (isdigit((int)(*p))) { uj = 10 * uj + (unsigned int)(*p++ - '0'); } precision = uj; } } /* parse 'h', 'l' and 'll' length modifiers */ if (*p == 'h' || *p == 'l') { length_modifier = *p; p++; if (length_modifier == 'l' && *p == 'l') { /* double l = long long */ #ifdef SNPRINTF_LONGLONG_SUPPORT length_modifier = '2'; /* double l encoded as '2' */ #else length_modifier = 'l'; /* treat it as a single 'l' */ #endif p++; } } fmt_spec = *p; /* common synonyms: */ switch (fmt_spec) { case 'i': fmt_spec = 'd'; break; case 'D': fmt_spec = 'd'; length_modifier = 'l'; break; case 'U': fmt_spec = 'u'; length_modifier = 'l'; break; case 'O': fmt_spec = 'o'; length_modifier = 'l'; break; default: break; } /* get parameter value, do initial processing */ switch (fmt_spec) { case '%': /* % behaves similar to 's' regarding flags and field widths */ case 'c': /* c behaves similar to 's' regarding flags and field widths */ case 's': length_modifier = '\0'; /* wint_t and wchar_t not supported */ /* the result of zero padding flag with non-numeric conversion specifier*/ /* is undefined. Solaris and HPUX 10 does zero padding in this case, */ /* Digital Unix and Linux does not. */ #if !defined(SOLARIS_COMPATIBLE) && !defined(HPUX_COMPATIBLE) zero_padding = 0; /* turn zero padding off for string conversions */ #endif str_arg_l = 1; switch (fmt_spec) { case '%': str_arg = p; break; case 'c': { int j = va_arg(ap, int); uchar_arg = (unsigned char) j; /* standard demands unsigned char */ str_arg = (const char *) &uchar_arg; break; } case 's': str_arg = va_arg(ap, const char *); if (!str_arg) { str_arg_l = 0; } /* make sure not to address string beyond the specified precision !!! */ else if (!precision_specified) { str_arg_l = strlen(str_arg); } /* truncate string if necessary as requested by precision */ else if (precision == 0) { str_arg_l = 0; } else { /* memchr on HP does not like n > 2^31 !!! */ const char *q = memchr(str_arg, '\0', precision <= 0x7fffffff ? precision : 0x7fffffff); str_arg_l = !q ? precision : (q - str_arg); } break; default: break; } break; case 'd': case 'u': case 'o': case 'x': case 'X': case 'p': { /* NOTE: the u, o, x, X and p conversion specifiers imply the value is unsigned; d implies a signed value */ int arg_sign = 0; /* 0 if numeric argument is zero (or if pointer is NULL for 'p'), +1 if greater than zero (or nonzero for unsigned arguments), -1 if negative (unsigned argument is never negative) */ int int_arg = 0; unsigned int uint_arg = 0; /* only defined for length modifier h, or for no length modifiers */ long int long_arg = 0; unsigned long int ulong_arg = 0; /* only defined for length modifier l */ void *ptr_arg = NULL; /* pointer argument value -only defined for p conversion */ #ifdef SNPRINTF_LONGLONG_SUPPORT long long int long_long_arg = 0; unsigned long long int ulong_long_arg = 0; /* only defined for length modifier ll */ #endif if (fmt_spec == 'p') { /* HPUX 10: An l, h, ll or L before any other conversion character * (other than d, i, u, o, x, or X) is ignored. * Digital Unix: * not specified, but seems to behave as HPUX does. * Solaris: If an h, l, or L appears before any other conversion * specifier (other than d, i, u, o, x, or X), the behavior * is undefined. (Actually %hp converts only 16-bits of address * and %llp treats address as 64-bit data which is incompatible * with (void *) argument on a 32-bit system). */ #ifdef SOLARIS_COMPATIBLE # ifdef SOLARIS_BUG_COMPATIBLE /* keep length modifiers even if it represents 'll' */ # else if (length_modifier == '2') { length_modifier = '\0'; } # endif #else length_modifier = '\0'; #endif ptr_arg = va_arg(ap, void *); if (ptr_arg != NULL) { arg_sign = 1; } } else if (fmt_spec == 'd') { /* signed */ switch (length_modifier) { case '\0': case 'h': /* It is non-portable to specify a second argument of char or short * to va_arg, because arguments seen by the called function * are not char or short. C converts char and short arguments * to int before passing them to a function. */ int_arg = va_arg(ap, int); if (int_arg > 0) { arg_sign = 1; } else if (int_arg < 0) { arg_sign = -1; } break; case 'l': long_arg = va_arg(ap, long int); if (long_arg > 0) { arg_sign = 1; } else if (long_arg < 0) { arg_sign = -1; } break; #ifdef SNPRINTF_LONGLONG_SUPPORT case '2': long_long_arg = va_arg(ap, long long int); if (long_long_arg > 0) { arg_sign = 1; } else if (long_long_arg < 0) { arg_sign = -1; } break; #endif } } else { /* unsigned */ switch (length_modifier) { case '\0': case 'h': uint_arg = va_arg(ap, unsigned int); if (uint_arg) { arg_sign = 1; } break; case 'l': ulong_arg = va_arg(ap, unsigned long int); if (ulong_arg) { arg_sign = 1; } break; #ifdef SNPRINTF_LONGLONG_SUPPORT case '2': ulong_long_arg = va_arg(ap, unsigned long long int); if (ulong_long_arg) { arg_sign = 1; } break; #endif } } str_arg = tmp; str_arg_l = 0; /* NOTE: * For d, i, u, o, x, and X conversions, if precision is specified, * the '0' flag should be ignored. This is so with Solaris 2.6, * Digital UNIX 4.0, HPUX 10, Linux, FreeBSD, NetBSD; but not with Perl. */ #ifndef PERL_COMPATIBLE if (precision_specified) { zero_padding = 0; } #endif if (fmt_spec == 'd') { if (force_sign && arg_sign >= 0) { tmp[str_arg_l++] = space_for_positive ? ' ' : '+'; } /* leave negative numbers for sprintf to handle, to avoid handling tricky cases like (short int)(-32768) */ #ifdef LINUX_COMPATIBLE } else if (fmt_spec == 'p' && force_sign && arg_sign > 0) { tmp[str_arg_l++] = space_for_positive ? ' ' : '+'; #endif } else if (alternate_form) { if (arg_sign != 0 && (fmt_spec == 'x' || fmt_spec == 'X')) { tmp[str_arg_l++] = '0'; tmp[str_arg_l++] = fmt_spec; } /* alternate form should have no effect for p conversion, but ... */ #ifdef HPUX_COMPATIBLE else if (fmt_spec == 'p' /* HPUX 10: for an alternate form of p conversion, * a nonzero result is prefixed by 0x. */ #ifndef HPUX_BUG_COMPATIBLE /* Actually it uses 0x prefix even for a zero value. */ && arg_sign != 0 #endif ) { tmp[str_arg_l++] = '0'; tmp[str_arg_l++] = 'x'; } #endif } zero_padding_insertion_ind = str_arg_l; if (!precision_specified) { precision = 1; /* default precision is 1 */ } if (precision == 0 && arg_sign == 0 #if defined(HPUX_BUG_COMPATIBLE) || defined(LINUX_COMPATIBLE) && fmt_spec != 'p' /* HPUX 10 man page claims: With conversion character p the result of * converting a zero value with a precision of zero is a null string. * Actually HP returns all zeroes, and Linux returns "(nil)". */ #endif ) { /* converted to null string */ /* When zero value is formatted with an explicit precision 0, the resulting formatted string is empty (d, i, u, o, x, X, p). */ } else { char f[5]; int f_l = 0; f[f_l++] = '%'; /* construct a simple format string for sprintf */ if (!length_modifier) { } else if (length_modifier == '2') { f[f_l++] = 'l'; f[f_l++] = 'l'; } else { f[f_l++] = length_modifier; } f[f_l++] = fmt_spec; f[f_l++] = '\0'; if (fmt_spec == 'p') { str_arg_l += duer_sprintf(tmp + str_arg_l, f, ptr_arg); } else if (fmt_spec == 'd') { /* signed */ switch (length_modifier) { case '\0': case 'h': str_arg_l += duer_sprintf(tmp + str_arg_l, f, int_arg); break; case 'l': str_arg_l += duer_sprintf(tmp + str_arg_l, f, long_arg); break; #ifdef SNPRINTF_LONGLONG_SUPPORT case '2': str_arg_l += duer_sprintf(tmp + str_arg_l, f, long_long_arg); break; #endif } } else { /* unsigned */ switch (length_modifier) { case '\0': case 'h': str_arg_l += duer_sprintf(tmp + str_arg_l, f, uint_arg); break; case 'l': str_arg_l += duer_sprintf(tmp + str_arg_l, f, ulong_arg); break; #ifdef SNPRINTF_LONGLONG_SUPPORT case '2': str_arg_l += duer_sprintf(tmp + str_arg_l, f, ulong_long_arg); break; #endif } } /* include the optional minus sign and possible "0x" in the region before the zero padding insertion point */ if (zero_padding_insertion_ind < str_arg_l && tmp[zero_padding_insertion_ind] == '-') { zero_padding_insertion_ind++; } if (zero_padding_insertion_ind + 1 < str_arg_l && tmp[zero_padding_insertion_ind] == '0' && (tmp[zero_padding_insertion_ind + 1] == 'x' || tmp[zero_padding_insertion_ind + 1] == 'X')) { zero_padding_insertion_ind += 2; } } { size_t num_of_digits = str_arg_l - zero_padding_insertion_ind; if (alternate_form && fmt_spec == 'o' #ifdef HPUX_COMPATIBLE /* ("%#.o",0) -> "" */ && (str_arg_l > 0) #endif #ifdef DIGITAL_UNIX_BUG_COMPATIBLE /* ("%#o",0) -> "00" */ #else /* unless zero is already the first character */ && !(zero_padding_insertion_ind < str_arg_l && tmp[zero_padding_insertion_ind] == '0') #endif ) { /* assure leading zero for alternate-form octal numbers */ if (!precision_specified || precision < num_of_digits + 1) { /* precision is increased to force the first character to be zero, except if a zero value is formatted with an explicit precision of zero */ precision = num_of_digits + 1; precision_specified = 1; } } /* zero padding to specified precision? */ if (num_of_digits < precision) { number_of_zeros_to_pad = precision - num_of_digits; } } /* zero padding to specified minimal field width? */ if (!justify_left && zero_padding) { int n = min_field_width - (str_arg_l + number_of_zeros_to_pad); if (n > 0) { number_of_zeros_to_pad += n; } } break; } default: /* unrecognized conversion specifier, keep format string as-is*/ zero_padding = 0; /* turn zero padding off for non-numeric convers. */ #ifndef DIGITAL_UNIX_COMPATIBLE justify_left = 1; min_field_width = 0; /* reset flags */ #endif #if defined(PERL_COMPATIBLE) || defined(LINUX_COMPATIBLE) /* keep the entire format string unchanged */ str_arg = starting_p; str_arg_l = p - starting_p; /* well, not exactly so for Linux, which does something inbetween, * and I don't feel an urge to imitate it: "%+++++hy" -> "%+y" */ #else /* discard the unrecognized conversion, just keep * * the unrecognized conversion character */ str_arg = p; str_arg_l = 0; #endif if (*p) { str_arg_l++; } /* include invalid conversion specifier unchanged if not at end-of-string */ break; } if (*p) { p++; /* step over the just processed conversion specifier */ } // insert padding to the left as requested by min_field_width // this does not include the zero padding in case of numerical conversions if (!justify_left) { /* left padding with blank or zero */ int n = min_field_width - (str_arg_l + number_of_zeros_to_pad); if (n > 0) { if (str_l < str_m) { size_t avail = str_m - str_l; fast_memset(str + str_l, (zero_padding ? '0' : ' '), (n > avail ? avail : n)); } str_l += n; } } // zero padding as requested by the precision or by the minimal field width // for numeric conversions required? if (number_of_zeros_to_pad <= 0) { // will not copy first part of numeric right now, // force it to be copied later in its entirety zero_padding_insertion_ind = 0; } else { // insert first part of numerics (sign or '0x') before zero padding int n = zero_padding_insertion_ind; if (n > 0) { if (str_l < str_m) { size_t avail = str_m - str_l; fast_memcpy(str + str_l, str_arg, (n > avail ? avail : n)); } str_l += n; } // insert zero padding as requested by the precision or min field width n = number_of_zeros_to_pad; if (n > 0) { if (str_l < str_m) { size_t avail = str_m - str_l; fast_memset(str + str_l, '0', (n > avail ? avail : n)); } str_l += n; } } // insert formatted string // (or as-is conversion specifier for unknown conversions) { int n = str_arg_l - zero_padding_insertion_ind; if (n > 0) { if (str_l < str_m) { size_t avail = str_m - str_l; fast_memcpy(str + str_l, str_arg + zero_padding_insertion_ind, (n > avail ? avail : n)); } str_l += n; } } /* insert right padding */ if (justify_left) { /* right blank padding to the field width */ int n = min_field_width - (str_arg_l + number_of_zeros_to_pad); if (n > 0) { if (str_l < str_m) { size_t avail = str_m - str_l; fast_memset(str + str_l, ' ', (n > avail ? avail : n)); } str_l += n; } } } } if (str_m > 0) { // make sure the string is null-terminated // even at the expense of overwriting the last character // (shouldn't happen, but just in case) str[str_l <= str_m - 1 ? str_l : str_m - 1] = '\0'; } // Return the number of characters formatted (excluding trailing null // character), that is, the number of characters that would have been // written to the buffer if it were large enough. // // The value of str_l should be returned, but str_l is of unsigned type // size_t, and snprintf is int, possibly leading to an undetected // integer overflow, resulting in a negative return value, which is illegal. // Both XSH5 and ISO C99 (at least the draft) are silent on this issue. // Should errno be set to EOVERFLOW and EOF returned in this case??? return (int) str_l; } int lightduer_snprintf(char *str, size_t str_m, const char *fmt, ...) { va_list ap; va_start(ap, fmt); int ret = lightduer_vsnprintf(str, str_m, fmt, ap); va_end(ap); return ret; } #endif // ENABLE_LIGHTDUER_SNPRINTF