
I started out with the idea that we needed to detect use of %m format specs in contexts other than elog/ereport calls, because we couldn't rely on that working in *printf calls. But a better answer is to fix things so that it does work. Now that we're using snprintf.c all the time, we can implement %m in that and we've fixed the problem. This requires also adjusting our various printf-wrapping functions so that they ensure "errno" is preserved when they call snprintf.c. Remove elog.c's handmade implementation of %m, and let it rely on snprintf to support the feature. That should provide some performance gain, though I've not attempted to measure it. There are a lot of places where we could now simplify 'printf("%s", strerror(errno))' into 'printf("%m")', but I'm not in any big hurry to make that happen. Patch by me, reviewed by Michael Paquier Discussion: https://postgr.es/m/2975.1526862605@sss.pgh.pa.us
315 lines
7.9 KiB
C
315 lines
7.9 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* stringinfo.c
|
|
*
|
|
* StringInfo provides an indefinitely-extensible string data type.
|
|
* It can be used to buffer either ordinary C strings (null-terminated text)
|
|
* or arbitrary binary data. All storage is allocated with palloc().
|
|
*
|
|
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* src/backend/lib/stringinfo.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "lib/stringinfo.h"
|
|
#include "utils/memutils.h"
|
|
|
|
|
|
/*
|
|
* makeStringInfo
|
|
*
|
|
* Create an empty 'StringInfoData' & return a pointer to it.
|
|
*/
|
|
StringInfo
|
|
makeStringInfo(void)
|
|
{
|
|
StringInfo res;
|
|
|
|
res = (StringInfo) palloc(sizeof(StringInfoData));
|
|
|
|
initStringInfo(res);
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* initStringInfo
|
|
*
|
|
* Initialize a StringInfoData struct (with previously undefined contents)
|
|
* to describe an empty string.
|
|
*/
|
|
void
|
|
initStringInfo(StringInfo str)
|
|
{
|
|
int size = 1024; /* initial default buffer size */
|
|
|
|
str->data = (char *) palloc(size);
|
|
str->maxlen = size;
|
|
resetStringInfo(str);
|
|
}
|
|
|
|
/*
|
|
* resetStringInfo
|
|
*
|
|
* Reset the StringInfo: the data buffer remains valid, but its
|
|
* previous content, if any, is cleared.
|
|
*/
|
|
void
|
|
resetStringInfo(StringInfo str)
|
|
{
|
|
str->data[0] = '\0';
|
|
str->len = 0;
|
|
str->cursor = 0;
|
|
}
|
|
|
|
/*
|
|
* appendStringInfo
|
|
*
|
|
* Format text data under the control of fmt (an sprintf-style format string)
|
|
* and append it to whatever is already in str. More space is allocated
|
|
* to str if necessary. This is sort of like a combination of sprintf and
|
|
* strcat.
|
|
*/
|
|
void
|
|
appendStringInfo(StringInfo str, const char *fmt,...)
|
|
{
|
|
int save_errno = errno;
|
|
|
|
for (;;)
|
|
{
|
|
va_list args;
|
|
int needed;
|
|
|
|
/* Try to format the data. */
|
|
errno = save_errno;
|
|
va_start(args, fmt);
|
|
needed = appendStringInfoVA(str, fmt, args);
|
|
va_end(args);
|
|
|
|
if (needed == 0)
|
|
break; /* success */
|
|
|
|
/* Increase the buffer size and try again. */
|
|
enlargeStringInfo(str, needed);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* appendStringInfoVA
|
|
*
|
|
* Attempt to format text data under the control of fmt (an sprintf-style
|
|
* format string) and append it to whatever is already in str. If successful
|
|
* return zero; if not (because there's not enough space), return an estimate
|
|
* of the space needed, without modifying str. Typically the caller should
|
|
* pass the return value to enlargeStringInfo() before trying again; see
|
|
* appendStringInfo for standard usage pattern.
|
|
*
|
|
* Caution: callers must be sure to preserve their entry-time errno
|
|
* when looping, in case the fmt contains "%m".
|
|
*
|
|
* 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.
|
|
*/
|
|
int
|
|
appendStringInfoVA(StringInfo str, const char *fmt, va_list args)
|
|
{
|
|
int avail;
|
|
size_t nprinted;
|
|
|
|
Assert(str != NULL);
|
|
|
|
/*
|
|
* If there's hardly any space, don't bother trying, just fail to make the
|
|
* caller enlarge the buffer first. We have to guess at how much to
|
|
* enlarge, since we're skipping the formatting work.
|
|
*/
|
|
avail = str->maxlen - str->len;
|
|
if (avail < 16)
|
|
return 32;
|
|
|
|
nprinted = pvsnprintf(str->data + str->len, (size_t) avail, fmt, args);
|
|
|
|
if (nprinted < (size_t) avail)
|
|
{
|
|
/* Success. Note nprinted does not include trailing null. */
|
|
str->len += (int) nprinted;
|
|
return 0;
|
|
}
|
|
|
|
/* Restore the trailing null so that str is unmodified. */
|
|
str->data[str->len] = '\0';
|
|
|
|
/*
|
|
* Return pvsnprintf's estimate of the space needed. (Although this is
|
|
* given as a size_t, we know it will fit in int because it's not more
|
|
* than MaxAllocSize.)
|
|
*/
|
|
return (int) nprinted;
|
|
}
|
|
|
|
/*
|
|
* appendStringInfoString
|
|
*
|
|
* Append a null-terminated string to str.
|
|
* Like appendStringInfo(str, "%s", s) but faster.
|
|
*/
|
|
void
|
|
appendStringInfoString(StringInfo str, const char *s)
|
|
{
|
|
appendBinaryStringInfo(str, s, strlen(s));
|
|
}
|
|
|
|
/*
|
|
* appendStringInfoChar
|
|
*
|
|
* Append a single byte to str.
|
|
* Like appendStringInfo(str, "%c", ch) but much faster.
|
|
*/
|
|
void
|
|
appendStringInfoChar(StringInfo str, char ch)
|
|
{
|
|
/* Make more room if needed */
|
|
if (str->len + 1 >= str->maxlen)
|
|
enlargeStringInfo(str, 1);
|
|
|
|
/* OK, append the character */
|
|
str->data[str->len] = ch;
|
|
str->len++;
|
|
str->data[str->len] = '\0';
|
|
}
|
|
|
|
/*
|
|
* appendStringInfoSpaces
|
|
*
|
|
* Append the specified number of spaces to a buffer.
|
|
*/
|
|
void
|
|
appendStringInfoSpaces(StringInfo str, int count)
|
|
{
|
|
if (count > 0)
|
|
{
|
|
/* Make more room if needed */
|
|
enlargeStringInfo(str, count);
|
|
|
|
/* OK, append the spaces */
|
|
while (--count >= 0)
|
|
str->data[str->len++] = ' ';
|
|
str->data[str->len] = '\0';
|
|
}
|
|
}
|
|
|
|
/*
|
|
* appendBinaryStringInfo
|
|
*
|
|
* Append arbitrary binary data to a StringInfo, allocating more space
|
|
* if necessary. Ensures that a trailing null byte is present.
|
|
*/
|
|
void
|
|
appendBinaryStringInfo(StringInfo str, const char *data, int datalen)
|
|
{
|
|
Assert(str != NULL);
|
|
|
|
/* Make more room if needed */
|
|
enlargeStringInfo(str, datalen);
|
|
|
|
/* OK, append the data */
|
|
memcpy(str->data + str->len, data, datalen);
|
|
str->len += datalen;
|
|
|
|
/*
|
|
* Keep a trailing null in place, even though it's probably useless for
|
|
* binary data. (Some callers are dealing with text but call this because
|
|
* their input isn't null-terminated.)
|
|
*/
|
|
str->data[str->len] = '\0';
|
|
}
|
|
|
|
/*
|
|
* appendBinaryStringInfoNT
|
|
*
|
|
* Append arbitrary binary data to a StringInfo, allocating more space
|
|
* if necessary. Does not ensure a trailing null-byte exists.
|
|
*/
|
|
void
|
|
appendBinaryStringInfoNT(StringInfo str, const char *data, int datalen)
|
|
{
|
|
Assert(str != NULL);
|
|
|
|
/* Make more room if needed */
|
|
enlargeStringInfo(str, datalen);
|
|
|
|
/* OK, append the data */
|
|
memcpy(str->data + str->len, data, datalen);
|
|
str->len += datalen;
|
|
}
|
|
|
|
/*
|
|
* enlargeStringInfo
|
|
*
|
|
* Make sure there is enough space for 'needed' more bytes
|
|
* ('needed' does not include the terminating null).
|
|
*
|
|
* External callers usually need not concern themselves with this, since
|
|
* all stringinfo.c routines do it automatically. However, if a caller
|
|
* knows that a StringInfo will eventually become X bytes large, it
|
|
* can save some palloc overhead by enlarging the buffer before starting
|
|
* to store data in it.
|
|
*
|
|
* NB: because we use repalloc() to enlarge the buffer, the string buffer
|
|
* will remain allocated in the same memory context that was current when
|
|
* initStringInfo was called, even if another context is now current.
|
|
* This is the desired and indeed critical behavior!
|
|
*/
|
|
void
|
|
enlargeStringInfo(StringInfo str, int needed)
|
|
{
|
|
int newlen;
|
|
|
|
/*
|
|
* Guard against out-of-range "needed" values. Without this, we can get
|
|
* an overflow or infinite loop in the following.
|
|
*/
|
|
if (needed < 0) /* should not happen */
|
|
elog(ERROR, "invalid string enlargement request size: %d", needed);
|
|
if (((Size) needed) >= (MaxAllocSize - (Size) str->len))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
|
errmsg("out of memory"),
|
|
errdetail("Cannot enlarge string buffer containing %d bytes by %d more bytes.",
|
|
str->len, needed)));
|
|
|
|
needed += str->len + 1; /* total space required now */
|
|
|
|
/* Because of the above test, we now have needed <= MaxAllocSize */
|
|
|
|
if (needed <= str->maxlen)
|
|
return; /* got enough space already */
|
|
|
|
/*
|
|
* We don't want to allocate just a little more space with each append;
|
|
* for efficiency, double the buffer size each time it overflows.
|
|
* Actually, we might need to more than double it if 'needed' is big...
|
|
*/
|
|
newlen = 2 * str->maxlen;
|
|
while (needed > newlen)
|
|
newlen = 2 * newlen;
|
|
|
|
/*
|
|
* Clamp to MaxAllocSize in case we went past it. Note we are assuming
|
|
* here that MaxAllocSize <= INT_MAX/2, else the above loop could
|
|
* overflow. We will still have newlen >= needed.
|
|
*/
|
|
if (newlen > (int) MaxAllocSize)
|
|
newlen = (int) MaxAllocSize;
|
|
|
|
str->data = (char *) repalloc(str->data, newlen);
|
|
|
|
str->maxlen = newlen;
|
|
}
|