diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2013-10-22 18:42:13 -0400 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2013-10-22 18:42:13 -0400 |
commit | 09a89cb5fc29b47c26d151e82293fd3bef592b7b (patch) | |
tree | 0ac288fac9c8b34730cca74bd90afa06752988ce /src/common/psprintf.c | |
parent | 586a8fc75bf266214d635cdcf527176b80f808ea (diff) | |
download | postgresql-09a89cb5fc29b47c26d151e82293fd3bef592b7b.tar.gz postgresql-09a89cb5fc29b47c26d151e82293fd3bef592b7b.zip |
Get rid of use of asprintf() in favor of a more portable implementation.
asprintf(), aside from not being particularly portable, has a fundamentally
badly-designed API; the psprintf() function that was added in passing in
the previous patch has a much better API choice. Moreover, the NetBSD
implementation that was borrowed for the previous patch doesn't work with
non-C99-compliant vsnprintf, which is something we still have to cope with
on some platforms; and it depends on va_copy which isn't all that portable
either. Get rid of that code in favor of an implementation similar to what
we've used for many years in stringinfo.c. Also, move it into libpgcommon
since it's not really libpgport material.
I think this patch will be enough to turn the buildfarm green again, but
there's still cosmetic work left to do, namely get rid of pg_asprintf()
in favor of using psprintf(). That will come in a followon patch.
Diffstat (limited to 'src/common/psprintf.c')
-rw-r--r-- | src/common/psprintf.c | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/src/common/psprintf.c b/src/common/psprintf.c new file mode 100644 index 00000000000..87fd013f840 --- /dev/null +++ b/src/common/psprintf.c @@ -0,0 +1,207 @@ +/*------------------------------------------------------------------------- + * + * psprintf.c + * sprintf into an allocated-on-demand buffer + * + * + * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/common/psprintf.c + * + *------------------------------------------------------------------------- + */ + +#ifndef FRONTEND +#include "postgres.h" +#else +#include "postgres_fe.h" +#endif + +#include "utils/memutils.h" + + +static size_t pvsnprintf(char *buf, size_t len, const char *fmt, va_list args) +__attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 0))); + + +/* + * psprintf + * + * Format text data under the control of fmt (an sprintf-style format string) + * and return it in an allocated-on-demand buffer. The buffer is allocated + * with palloc in the backend, or malloc in frontend builds. Caller is + * responsible to free the buffer when no longer needed, if appropriate. + * + * Errors are not returned to the caller, but are reported via elog(ERROR) + * in the backend, or printf-to-stderr-and-exit() in frontend builds. + * One should therefore think twice about using this in libpq. + */ +char * +psprintf(const char *fmt,...) +{ + size_t len = 128; /* initial assumption about buffer size */ + + for (;;) + { + char *result; + va_list args; + + /* + * Allocate result buffer. Note that in frontend this maps to malloc + * with exit-on-error. + */ + result = (char *) palloc(len); + + /* Try to format the data. */ + va_start(args, fmt); + len = pvsnprintf(result, len, fmt, args); + va_end(args); + + if (len == 0) + return result; /* success */ + + /* Release buffer and loop around to try again with larger len. */ + pfree(result); + } +} + +/* + * pvsnprintf + * + * Attempt to format text data under the control of fmt (an sprintf-style + * format string) and insert it into buf (which has length len). + * + * If successful, return zero. If there's not enough space in buf, return + * an estimate of the buffer size needed to succeed (this *must* be more + * than "len", else psprintf might loop infinitely). + * Other error cases do not return. + * + * XXX This API is ugly, but there seems no alternative given the C spec's + * restrictions on what can portably be done with va_list arguments: you have + * to redo va_start before you can rescan the argument list, and we can't do + * that from here. + */ +static size_t +pvsnprintf(char *buf, size_t len, const char *fmt, va_list args) +{ + int nprinted; + + Assert(len > 0); + + errno = 0; + + /* + * Assert check here is to catch buggy vsnprintf that overruns the + * specified buffer length. Solaris 7 in 64-bit mode is an example of a + * platform with such a bug. + */ +#ifdef USE_ASSERT_CHECKING + buf[len - 1] = '\0'; +#endif + + nprinted = vsnprintf(buf, len, fmt, args); + + Assert(buf[len - 1] == '\0'); + + /* + * If vsnprintf reports an error other than ENOMEM, fail. The possible + * causes of this are not user-facing errors, so elog should be enough. + */ + if (nprinted < 0 && errno != 0 && errno != ENOMEM) + { +#ifndef FRONTEND + elog(ERROR, "vsnprintf failed: %m"); +#else + fprintf(stderr, "vsnprintf failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); +#endif + } + + /* + * Note: some versions of vsnprintf return the number of chars actually + * stored, not the total space needed as C99 specifies. And at least one + * returns -1 on failure. Be conservative about believing whether the + * print worked. + */ + if (nprinted >= 0 && (size_t) nprinted < len - 1) + { + /* Success. Note nprinted does not include trailing null. */ + return 0; + } + + if (nprinted >= 0 && (size_t) nprinted > len) + { + /* + * This appears to be a C99-compliant vsnprintf, so believe its + * estimate of the required space. (If it's wrong, this code will + * still work, but may loop multiple times.) Note that the space + * needed should be only nprinted+1 bytes, but we'd better allocate + * one more than that so that the test above will succeed next time. + * + * In the corner case where the required space just barely overflows, + * fall through so that we'll error out below (possibly after looping). + */ + if ((size_t) nprinted <= MaxAllocSize - 2) + return nprinted + 2; + } + + /* + * Buffer overrun, and we don't know how much space is needed. Estimate + * twice the previous buffer size. If this would overflow, choke. We use + * a palloc-oriented overflow limit even when in frontend. + */ + if (len > MaxAllocSize / 2) + { +#ifndef FRONTEND + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); +#else + fprintf(stderr, _("out of memory\n")); + exit(EXIT_FAILURE); +#endif + } + + return len * 2; +} + + +/* + * XXX this is going away shortly. + */ +#ifdef FRONTEND +int +pg_asprintf(char **ret, const char *fmt, ...) +{ + size_t len = 128; /* initial assumption about buffer size */ + + for (;;) + { + char *result; + va_list args; + + /* + * Allocate result buffer. Note that in frontend this maps to malloc + * with exit-on-error. + */ + result = (char *) palloc(len); + + /* Try to format the data. */ + va_start(args, fmt); + len = pvsnprintf(result, len, fmt, args); + va_end(args); + + if (len == 0) + { + *ret = result; + return 0; + } + + /* Release buffer and loop around to try again with larger len. */ + pfree(result); + } +} +#endif |