| /*------------------------------------------------------------------------- |
| * |
| * stringinfo.c |
| * |
| * StringInfo provides an extensible string data type (currently limited to a |
| * length of 1GB). It can be used to buffer either ordinary C strings |
| * (null-terminated text) or arbitrary binary data. All storage is allocated |
| * with palloc() (falling back to malloc in frontend code). |
| * |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * src/common/stringinfo.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| |
| #ifndef FRONTEND |
| |
| #include "postgres.h" |
| #include "utils/memutils.h" |
| |
| #else |
| |
| #include "postgres_fe.h" |
| |
| /* It's possible we could use a different value for this in frontend code */ |
| #define MaxAllocSize ((Size) 0x3fffffff) /* 1 gigabyte - 1 */ |
| |
| #endif |
| |
| #include "lib/stringinfo.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); |
| } |
| |
| /* |
| * 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; |
| } |
| |
| /* |
| * 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 */ |
| memset(&str->data[str->len], ' ', count); |
| str->len += count; |
| 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 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. (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 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; |
| } |
| |
| /* |
| * 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: In the backend, 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 */ |
| { |
| #ifndef FRONTEND |
| elog(ERROR, "invalid string enlargement request size: %d", needed); |
| #else |
| fprintf(stderr, "invalid string enlargement request size: %d\n", needed); |
| exit(EXIT_FAILURE); |
| #endif |
| } |
| if (((Size) needed) >= (MaxAllocSize - (Size) str->len)) |
| { |
| #ifndef FRONTEND |
| 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))); |
| #else |
| fprintf(stderr, |
| _("out of memory\n\nCannot enlarge string buffer containing %d bytes by %d more bytes.\n"), |
| str->len, needed); |
| exit(EXIT_FAILURE); |
| #endif |
| } |
| |
| 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; |
| } |
| |
| /* |
| * 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); |
| } |
| } |