Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pqexpbuffer.c
4 : : *
5 : : * PQExpBuffer provides an indefinitely-extensible string data type.
6 : : * It can be used to buffer either ordinary C strings (null-terminated text)
7 : : * or arbitrary binary data. All storage is allocated with malloc().
8 : : *
9 : : * This module is essentially the same as the backend's StringInfo data type,
10 : : * but it is intended for use in frontend libpq and client applications.
11 : : * Thus, it does not rely on palloc() nor elog(), nor psprintf.c which
12 : : * will exit() on error.
13 : : *
14 : : * It does rely on vsnprintf(); if configure finds that libc doesn't provide
15 : : * a usable vsnprintf(), then a copy of our own implementation of it will
16 : : * be linked into libpq.
17 : : *
18 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
19 : : * Portions Copyright (c) 1994, Regents of the University of California
20 : : *
21 : : * src/interfaces/libpq/pqexpbuffer.c
22 : : *
23 : : *-------------------------------------------------------------------------
24 : : */
25 : :
26 : : #include "postgres_fe.h"
27 : :
28 : : #include <limits.h>
29 : :
30 : : #include "pqexpbuffer.h"
31 : :
32 : : #ifdef WIN32
33 : : #include "win32.h"
34 : : #endif
35 : :
36 : :
37 : : /* All "broken" PQExpBuffers point to this string. */
38 : : static const char oom_buffer[1] = "";
39 : :
40 : : /* Need a char * for unconstify() compatibility */
41 : : static const char *const oom_buffer_ptr = oom_buffer;
42 : :
43 : :
44 : : /*
45 : : * markPQExpBufferBroken
46 : : *
47 : : * Put a PQExpBuffer in "broken" state if it isn't already.
48 : : */
49 : : static void
5618 tgl@sss.pgh.pa.us 50 :UBC 0 : markPQExpBufferBroken(PQExpBuffer str)
51 : : {
52 [ # # ]: 0 : if (str->data != oom_buffer)
53 : 0 : free(str->data);
54 : :
55 : : /*
56 : : * Casting away const here is a bit ugly, but it seems preferable to not
57 : : * marking oom_buffer const. We want to do that to encourage the compiler
58 : : * to put oom_buffer in read-only storage, so that anyone who tries to
59 : : * scribble on a broken PQExpBuffer will get a failure.
60 : : */
1902 peter@eisentraut.org 61 : 0 : str->data = unconstify(char *, oom_buffer_ptr);
5618 tgl@sss.pgh.pa.us 62 : 0 : str->len = 0;
63 : 0 : str->maxlen = 0;
64 : 0 : }
65 : :
66 : : /*
67 : : * createPQExpBuffer
68 : : *
69 : : * Create an empty 'PQExpBufferData' & return a pointer to it.
70 : : */
71 : : PQExpBuffer
8993 tgl@sss.pgh.pa.us 72 :CBC 359969 : createPQExpBuffer(void)
73 : : {
74 : : PQExpBuffer res;
75 : :
76 : 359969 : res = (PQExpBuffer) malloc(sizeof(PQExpBufferData));
77 [ + - ]: 359969 : if (res != NULL)
78 : 359969 : initPQExpBuffer(res);
79 : :
80 : 359969 : return res;
81 : : }
82 : :
83 : : /*
84 : : * initPQExpBuffer
85 : : *
86 : : * Initialize a PQExpBufferData struct (with previously undefined contents)
87 : : * to describe an empty string.
88 : : */
89 : : void
90 : 774377 : initPQExpBuffer(PQExpBuffer str)
91 : : {
92 : 774377 : str->data = (char *) malloc(INITIAL_EXPBUFFER_SIZE);
93 [ - + ]: 774377 : if (str->data == NULL)
94 : : {
1789 tgl@sss.pgh.pa.us 95 :UBC 0 : str->data = unconstify(char *, oom_buffer_ptr); /* see comment above */
8993 96 : 0 : str->maxlen = 0;
97 : 0 : str->len = 0;
98 : : }
99 : : else
100 : : {
8993 tgl@sss.pgh.pa.us 101 :CBC 774377 : str->maxlen = INITIAL_EXPBUFFER_SIZE;
102 : 774377 : str->len = 0;
103 : 774377 : str->data[0] = '\0';
104 : : }
105 : 774377 : }
106 : :
107 : : /*
108 : : * destroyPQExpBuffer(str);
109 : : *
110 : : * free()s both the data buffer and the PQExpBufferData.
111 : : * This is the inverse of createPQExpBuffer().
112 : : */
113 : : void
114 : 351069 : destroyPQExpBuffer(PQExpBuffer str)
115 : : {
116 [ + + ]: 351069 : if (str)
117 : : {
118 : 350930 : termPQExpBuffer(str);
119 : 350930 : free(str);
120 : : }
121 : 351069 : }
122 : :
123 : : /*
124 : : * termPQExpBuffer(str)
125 : : * free()s the data buffer but not the PQExpBufferData itself.
126 : : * This is the inverse of initPQExpBuffer().
127 : : */
128 : : void
129 : 704181 : termPQExpBuffer(PQExpBuffer str)
130 : : {
5618 131 [ + - ]: 704181 : if (str->data != oom_buffer)
8993 132 : 704181 : free(str->data);
133 : : /* just for luck, make the buffer validly empty. */
1789 134 : 704181 : str->data = unconstify(char *, oom_buffer_ptr); /* see comment above */
8486 135 : 704181 : str->maxlen = 0;
136 : 704181 : str->len = 0;
8993 137 : 704181 : }
138 : :
139 : : /*
140 : : * resetPQExpBuffer
141 : : * Reset a PQExpBuffer to empty
142 : : *
143 : : * Note: if possible, a "broken" PQExpBuffer is returned to normal.
144 : : */
145 : : void
146 : 3381399 : resetPQExpBuffer(PQExpBuffer str)
147 : : {
148 [ + + ]: 3381399 : if (str)
149 : : {
5618 150 [ + - ]: 3380619 : if (str->data != oom_buffer)
151 : : {
152 : 3380619 : str->len = 0;
8993 153 : 3380619 : str->data[0] = '\0';
154 : : }
155 : : else
156 : : {
157 : : /* try to reinitialize to valid state */
5618 tgl@sss.pgh.pa.us 158 :UBC 0 : initPQExpBuffer(str);
159 : : }
160 : : }
8993 tgl@sss.pgh.pa.us 161 :CBC 3381399 : }
162 : :
163 : : /*
164 : : * enlargePQExpBuffer
165 : : * Make sure there is enough space for 'needed' more bytes in the buffer
166 : : * ('needed' does not include the terminating null).
167 : : *
168 : : * Returns 1 if OK, 0 if failed to enlarge buffer. (In the latter case
169 : : * the buffer is left in "broken" state.)
170 : : */
171 : : int
8833 peter_e@gmx.net 172 : 7609378 : enlargePQExpBuffer(PQExpBuffer str, size_t needed)
173 : : {
174 : : size_t newlen;
175 : : char *newdata;
176 : :
5618 tgl@sss.pgh.pa.us 177 [ + - - + ]: 7609378 : if (PQExpBufferBroken(str))
5618 tgl@sss.pgh.pa.us 178 :UBC 0 : return 0; /* already failed */
179 : :
180 : : /*
181 : : * Guard against ridiculous "needed" values, which can occur if we're fed
182 : : * bogus data. Without this, we can get an overflow or infinite loop in
183 : : * the following.
184 : : */
7275 tgl@sss.pgh.pa.us 185 [ - + ]:CBC 7609378 : if (needed >= ((size_t) INT_MAX - str->len))
186 : : {
5618 tgl@sss.pgh.pa.us 187 :UBC 0 : markPQExpBufferBroken(str);
7275 188 : 0 : return 0;
189 : : }
190 : :
8993 tgl@sss.pgh.pa.us 191 :CBC 7609378 : needed += str->len + 1; /* total space required now */
192 : :
193 : : /* Because of the above test, we now have needed <= INT_MAX */
194 : :
195 [ + + ]: 7609378 : if (needed <= str->maxlen)
196 : 7522960 : return 1; /* got enough space already */
197 : :
198 : : /*
199 : : * We don't want to allocate just a little more space with each append;
200 : : * for efficiency, double the buffer size each time it overflows.
201 : : * Actually, we might need to more than double it if 'needed' is big...
202 : : */
8486 203 [ + - ]: 86418 : newlen = (str->maxlen > 0) ? (2 * str->maxlen) : 64;
8993 204 [ + + ]: 89226 : while (needed > newlen)
205 : 2808 : newlen = 2 * newlen;
206 : :
207 : : /*
208 : : * Clamp to INT_MAX in case we went past it. Note we are assuming here
209 : : * that INT_MAX <= UINT_MAX/2, else the above loop could overflow. We
210 : : * will still have newlen >= needed.
211 : : */
7275 212 [ - + ]: 86418 : if (newlen > (size_t) INT_MAX)
7275 tgl@sss.pgh.pa.us 213 :UBC 0 : newlen = (size_t) INT_MAX;
214 : :
8993 tgl@sss.pgh.pa.us 215 :CBC 86418 : newdata = (char *) realloc(str->data, newlen);
216 [ + - ]: 86418 : if (newdata != NULL)
217 : : {
218 : 86418 : str->data = newdata;
219 : 86418 : str->maxlen = newlen;
220 : 86418 : return 1;
221 : : }
222 : :
5618 tgl@sss.pgh.pa.us 223 :UBC 0 : markPQExpBufferBroken(str);
8993 224 : 0 : return 0;
225 : : }
226 : :
227 : : /*
228 : : * printfPQExpBuffer
229 : : * Format text data under the control of fmt (an sprintf-like format string)
230 : : * and insert it into str. More space is allocated to str if necessary.
231 : : * This is a convenience routine that does the same thing as
232 : : * resetPQExpBuffer() followed by appendPQExpBuffer().
233 : : */
234 : : void
8993 tgl@sss.pgh.pa.us 235 :CBC 235877 : printfPQExpBuffer(PQExpBuffer str, const char *fmt,...)
236 : : {
2027 237 : 235877 : int save_errno = errno;
238 : : va_list args;
239 : : bool done;
240 : :
8993 241 : 235877 : resetPQExpBuffer(str);
242 : :
5618 243 [ + - - + ]: 235877 : if (PQExpBufferBroken(str))
5618 tgl@sss.pgh.pa.us 244 :UBC 0 : return; /* already failed */
245 : :
246 : : /* Loop in case we have to retry after enlarging the buffer. */
247 : : do
248 : : {
2027 tgl@sss.pgh.pa.us 249 :CBC 238260 : errno = save_errno;
3824 250 : 238260 : va_start(args, fmt);
251 : 238260 : done = appendPQExpBufferVA(str, fmt, args);
252 : 238260 : va_end(args);
253 [ + + ]: 238260 : } while (!done);
254 : : }
255 : :
256 : : /*
257 : : * appendPQExpBuffer
258 : : *
259 : : * Format text data under the control of fmt (an sprintf-like format string)
260 : : * and append it to whatever is already in str. More space is allocated
261 : : * to str if necessary. This is sort of like a combination of sprintf and
262 : : * strcat.
263 : : */
264 : : void
8993 265 : 534214 : appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
266 : : {
2027 267 : 534214 : int save_errno = errno;
268 : : va_list args;
269 : : bool done;
270 : :
5618 271 [ + - - + ]: 534214 : if (PQExpBufferBroken(str))
5618 tgl@sss.pgh.pa.us 272 :UBC 0 : return; /* already failed */
273 : :
274 : : /* Loop in case we have to retry after enlarging the buffer. */
275 : : do
276 : : {
2027 tgl@sss.pgh.pa.us 277 :CBC 611287 : errno = save_errno;
3824 278 : 611287 : va_start(args, fmt);
279 : 611287 : done = appendPQExpBufferVA(str, fmt, args);
280 : 611287 : va_end(args);
281 [ + + ]: 611287 : } while (!done);
282 : : }
283 : :
284 : : /*
285 : : * appendPQExpBufferVA
286 : : * Shared guts of printfPQExpBuffer/appendPQExpBuffer.
287 : : * Attempt to format data and append it to str. Returns true if done
288 : : * (either successful or hard failure), false if need to retry.
289 : : *
290 : : * Caution: callers must be sure to preserve their entry-time errno
291 : : * when looping, in case the fmt contains "%m".
292 : : */
293 : : bool
294 : 850183 : appendPQExpBufferVA(PQExpBuffer str, const char *fmt, va_list args)
295 : : {
296 : : size_t avail;
297 : : size_t needed;
298 : : int nprinted;
299 : :
300 : : /*
301 : : * Try to format the given string into the available space; but if there's
302 : : * hardly any space, don't bother trying, just enlarge the buffer first.
303 : : */
304 [ + + ]: 850183 : if (str->maxlen > str->len + 16)
305 : : {
2068 306 : 848062 : avail = str->maxlen - str->len;
307 : :
3824 308 : 848062 : nprinted = vsnprintf(str->data + str->len, avail, fmt, args);
309 : :
310 : : /*
311 : : * If vsnprintf reports an error, fail (we assume this means there's
312 : : * something wrong with the format string).
313 : : */
2068 314 [ - + ]: 848062 : if (unlikely(nprinted < 0))
315 : : {
3824 tgl@sss.pgh.pa.us 316 :UBC 0 : markPQExpBufferBroken(str);
317 : 0 : return true;
318 : : }
319 : :
2068 tgl@sss.pgh.pa.us 320 [ + + ]:CBC 848062 : if ((size_t) nprinted < avail)
321 : : {
322 : : /* Success. Note nprinted does not include trailing null. */
3824 323 : 770726 : str->len += nprinted;
324 : 770726 : return true;
325 : : }
326 : :
327 : : /*
328 : : * We assume a C99-compliant vsnprintf, so believe its estimate of the
329 : : * required space, and add one for the trailing null. (If it's wrong,
330 : : * the logic will still work, but we may loop multiple times.)
331 : : *
332 : : * Choke if the required space would exceed INT_MAX, since str->maxlen
333 : : * can't represent more than that.
334 : : */
2068 335 [ - + ]: 77336 : if (unlikely(nprinted > INT_MAX - 1))
336 : : {
2068 tgl@sss.pgh.pa.us 337 :UBC 0 : markPQExpBufferBroken(str);
338 : 0 : return true;
339 : : }
2068 tgl@sss.pgh.pa.us 340 :CBC 77336 : needed = nprinted + 1;
341 : : }
342 : : else
343 : : {
344 : : /*
345 : : * We have to guess at how much to enlarge, since we're skipping the
346 : : * formatting work. Fortunately, because of enlargePQExpBuffer's
347 : : * preference for power-of-2 sizes, this number isn't very sensitive;
348 : : * the net effect is that we'll double the buffer size before trying
349 : : * to run vsnprintf, which seems sensible.
350 : : */
3824 351 : 2121 : needed = 32;
352 : : }
353 : :
354 : : /* Increase the buffer size and try again. */
355 [ - + ]: 79457 : if (!enlargePQExpBuffer(str, needed))
3824 tgl@sss.pgh.pa.us 356 :UBC 0 : return true; /* oops, out of memory */
357 : :
3824 tgl@sss.pgh.pa.us 358 :CBC 79457 : return false;
359 : : }
360 : :
361 : : /*
362 : : * appendPQExpBufferStr
363 : : * Append the given string to a PQExpBuffer, allocating more space
364 : : * if necessary.
365 : : */
366 : : void
8993 367 : 1076701 : appendPQExpBufferStr(PQExpBuffer str, const char *data)
368 : : {
369 : 1076701 : appendBinaryPQExpBuffer(str, data, strlen(data));
370 : 1076701 : }
371 : :
372 : : /*
373 : : * appendPQExpBufferChar
374 : : * Append a single byte to str.
375 : : * Like appendPQExpBuffer(str, "%c", ch) but much faster.
376 : : */
377 : : void
378 : 990157 : appendPQExpBufferChar(PQExpBuffer str, char ch)
379 : : {
380 : : /* Make more room if needed */
8768 bruce@momjian.us 381 [ - + ]: 990157 : if (!enlargePQExpBuffer(str, 1))
8993 tgl@sss.pgh.pa.us 382 :UBC 0 : return;
383 : :
384 : : /* OK, append the character */
8993 tgl@sss.pgh.pa.us 385 :CBC 990157 : str->data[str->len] = ch;
386 : 990157 : str->len++;
387 : 990157 : str->data[str->len] = '\0';
388 : : }
389 : :
390 : : /*
391 : : * appendBinaryPQExpBuffer
392 : : *
393 : : * Append arbitrary binary data to a PQExpBuffer, allocating more space
394 : : * if necessary.
395 : : */
396 : : void
8833 peter_e@gmx.net 397 : 6511791 : appendBinaryPQExpBuffer(PQExpBuffer str, const char *data, size_t datalen)
398 : : {
399 : : /* Make more room if needed */
8768 bruce@momjian.us 400 [ - + ]: 6511791 : if (!enlargePQExpBuffer(str, datalen))
8993 tgl@sss.pgh.pa.us 401 :UBC 0 : return;
402 : :
403 : : /* OK, append the data */
8993 tgl@sss.pgh.pa.us 404 :CBC 6511791 : memcpy(str->data + str->len, data, datalen);
405 : 6511791 : str->len += datalen;
406 : :
407 : : /*
408 : : * Keep a trailing null in place, even though it's probably useless for
409 : : * binary data...
410 : : */
411 : 6511791 : str->data[str->len] = '\0';
412 : : }
|