blob: 9e599e8a361d8b4277a13973ebdfc04bb896ec37 [file] [log] [blame]
/*-------------------------------------------------------------------------
*
* 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-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/lib/stringinfo.c,v 1.50 2009/01/01 17:23:42 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#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;
str->len = 0;
str->data[0] = '\0';
str->cursor = 0;
}
/*
* initStringInfoOfSize
*
* Initialize a StringInfoData struct with data buffer of 'size' bytes
*/
void
initStringInfoOfSize(StringInfo str, int size)
{
str->data = (char *) palloc(size);
str->maxlen = size;
str->len = 0;
str->data[0] = '\0';
str->cursor = 0;
}
/*
* initStringInfoOfString
*
* Initialize a StringInfoData struct with c string
*/
void
initStringInfoOfString(StringInfo str, char * buf, int size)
{
str->data = buf;
str->maxlen = size;
str->len = size;
str->cursor = 0;
}
/*
* 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,...)
{
for (;;)
{
va_list args;
bool success;
/* Try to format the data. */
va_start(args, fmt);
success = appendStringInfoVA(str, fmt, args);
va_end(args);
if (success)
break;
/* Double the buffer size and try again. */
enlargeStringInfo(str, str->maxlen);
}
}
/*
* 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 true; if not (because there's not enough space), return false
* without modifying str. Typically the caller would enlarge str and retry
* on false return --- see appendStringInfo for standard usage pattern.
*
* 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.
*/
bool
appendStringInfoVA(StringInfo str, const char *fmt, va_list args)
{
int avail,
nprinted;
Assert(str != NULL);
/*
* If there's hardly any space, don't bother trying, just fail to make the
* caller enlarge the buffer first.
*/
avail = str->maxlen - str->len - 1;
if (avail < 16)
return false;
/*
* 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
str->data[str->maxlen - 1] = '\0';
#endif
nprinted = vsnprintf(str->data + str->len, avail, fmt, args);
Assert(str->data[str->maxlen - 1] == '\0');
/*
* Note: some versions of vsnprintf return the number of chars actually
* stored, but at least one returns -1 on failure. Be conservative about
* believing whether the print worked.
*/
if (nprinted >= 0 && nprinted < avail - 1)
{
/* Success. Note nprinted does not include trailing null. */
str->len += nprinted;
return true;
}
/* Restore the trailing null so that str is unmodified. */
str->data[str->len] = '\0';
return false;
}
/*
* 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';
}
/*
* appendStringInfoFill
*
* Append a single byte, repeated 0 or more times, to str.
*/
void
appendStringInfoFill(StringInfo str, int occurrences, char ch)
{
/* Length must not overflow. */
if (str->len + occurrences <= str->len)
return;
/* Make more room if needed */
if (str->len + occurrences >= str->maxlen)
enlargeStringInfo(str, occurrences);
/* Fill specified number of bytes with the character. */
memset(str->data + str->len, ch, occurrences);
str->len += occurrences;
str->data[str->len] = '\0';
}
/*
* appendBinaryStringInfo
*
* Append arbitrary binary data to a StringInfo, allocating more space
* if necessary.
*/
void
appendBinaryStringInfo(StringInfo str, const void *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...
*/
str->data[str->len] = '\0';
}
/*
* 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)
{
/*
* Currently we support allocations only up to MaxAllocSize - 1
* (see AllocSizeIsValid()).
*/
newlen = (int) MaxAllocSize - 1;
}
str->data = (char *) repalloc(str->data, newlen);
str->maxlen = newlen;
}
/*------------------------
* truncateStringInfo
* Make sure a StringInfo's string is no longer than 'nchars' characters.
*/
void
truncateStringInfo(StringInfo str, int nchars)
{
if (str &&
str->len > nchars)
{
Assert(str->data != NULL &&
str->len <= str->maxlen);
str->len = nchars;
str->data[nchars] = '\0';
}
} /* truncateStringInfo */
/*
* Replace all occurrences of a string in a StringInfo with a different string.
*/
void
replaceStringInfoString(StringInfo str, char *replace, char *replacement)
{
char *ptr;
while ((ptr = strstr(str->data, replace)) != NULL)
{
char *dup = pstrdup(str->data);
resetStringInfo(str);
appendBinaryStringInfo(str, dup, ptr - str->data);
appendStringInfoString(str, replacement);
appendStringInfoString(str, dup + (ptr - str->data) + strlen(replace));
pfree(dup);
}
}