/* * Do an sprintf(), but into a custom-allocated buffer. * * Currently I'm doing this via vsnprintf. This has worked so far, * but it's not good, because vsnprintf is not available on all * platforms. There's an ifdef to use `_vsnprintf', which seems * to be the local name for it on Windows. Other platforms may * lack it completely, in which case it'll be time to rewrite * this function in a totally different way. * * The only `properly' portable solution I can think of is to * implement my own format string scanner, which figures out an * upper bound for the length of each formatting directive, * allocates the buffer as it goes along, and calls sprintf() to * actually process each directive. If I ever need to actually do * this, some caveats: * * - It's very hard to find a reliable upper bound for * floating-point values. %f, in particular, when supplied with * a number near to the upper or lower limit of representable * numbers, could easily take several hundred characters. It's * probably feasible to predict this statically using the * constants in , or even to predict it dynamically by * looking at the exponent of the specific float provided, but * it won't be fun. * * - Don't forget to _check_, after calling sprintf, that it's * used at most the amount of space we had available. * * - Fault any formatting directive we don't fully understand. The * aim here is to _guarantee_ that we never overflow the buffer, * because this is a security-critical function. If we see a * directive we don't know about, we should panic and die rather * than run any risk. */ #include #include "defs.h" #include "misc.h" #include "utils/utils.h" /* Work around lack of va_copy in old MSC */ #if defined _MSC_VER && !defined va_copy #define va_copy(a, b) TYPECHECK( \ (va_list *)0 == &(a) && (va_list *)0 == &(b), \ memcpy(&a, &b, sizeof(va_list))) #endif /* Also lack of vsnprintf before VS2015 */ #if defined _WINDOWS && \ !defined __MINGW32__ && \ !defined __WINE__ && \ _MSC_VER < 1900 #define vsnprintf _vsnprintf #endif char *dupvprintf_inner(char *buf, size_t oldlen, size_t *sizeptr, const char *fmt, va_list ap) { size_t size = *sizeptr; sgrowarrayn_nm(buf, size, oldlen, 512); while (1) { va_list aq; va_copy(aq, ap); int len = vsnprintf(buf + oldlen, size - oldlen, fmt, aq); va_end(aq); if (len >= 0 && len < size) { /* This is the C99-specified criterion for snprintf to have * been completely successful. */ *sizeptr = size; return buf; } else if (len > 0) { /* This is the C99 error condition: the returned length is * the required buffer size not counting the NUL. */ sgrowarrayn_nm(buf, size, oldlen + 1, len); } else { /* This is the pre-C99 glibc error condition: <0 means the * buffer wasn't big enough, so we enlarge it a bit and hope. */ sgrowarray_nm(buf, size, size); } } } char *dupvprintf(const char *fmt, va_list ap) { size_t size = 0; return dupvprintf_inner(NULL, 0, &size, fmt, ap); } char *dupprintf(const char *fmt, ...) { char *ret; va_list ap; va_start(ap, fmt); ret = dupvprintf(fmt, ap); va_end(ap); return ret; }