diff options
Diffstat (limited to 'utils/dupprintf.c')
-rw-r--r-- | utils/dupprintf.c | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/utils/dupprintf.c b/utils/dupprintf.c new file mode 100644 index 00000000..aa9f330b --- /dev/null +++ b/utils/dupprintf.c @@ -0,0 +1,100 @@ +/* + * 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 <float.h>, 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 <stdio.h> + +#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; +} |