1/*-------------------------------------------------------------------------
2 *
3 * psprintf.c
4 * sprintf into an allocated-on-demand buffer
5 *
6 *
7 * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
8 * Portions Copyright (c) 1994, Regents of the University of California
9 *
10 *
11 * IDENTIFICATION
12 * src/common/psprintf.c
13 *
14 *-------------------------------------------------------------------------
15 */
16
17#ifndef FRONTEND
18
19#include "postgres.h"
20
21#include "utils/memutils.h"
22
23#else
24
25#include "postgres_fe.h"
26
27/* It's possible we could use a different value for this in frontend code */
28#define MaxAllocSize ((Size) 0x3fffffff) /* 1 gigabyte - 1 */
29
30#endif
31
32
33/*
34 * psprintf
35 *
36 * Format text data under the control of fmt (an sprintf-style format string)
37 * and return it in an allocated-on-demand buffer. The buffer is allocated
38 * with palloc in the backend, or malloc in frontend builds. Caller is
39 * responsible to free the buffer when no longer needed, if appropriate.
40 *
41 * Errors are not returned to the caller, but are reported via elog(ERROR)
42 * in the backend, or printf-to-stderr-and-exit() in frontend builds.
43 * One should therefore think twice about using this in libpq.
44 */
45char *
46psprintf(const char *fmt,...)
47{
48 int save_errno = errno;
49 size_t len = 128; /* initial assumption about buffer size */
50
51 for (;;)
52 {
53 char *result;
54 va_list args;
55 size_t newlen;
56
57 /*
58 * Allocate result buffer. Note that in frontend this maps to malloc
59 * with exit-on-error.
60 */
61 result = (char *) palloc(len);
62
63 /* Try to format the data. */
64 errno = save_errno;
65 va_start(args, fmt);
66 newlen = pvsnprintf(result, len, fmt, args);
67 va_end(args);
68
69 if (newlen < len)
70 return result; /* success */
71
72 /* Release buffer and loop around to try again with larger len. */
73 pfree(result);
74 len = newlen;
75 }
76}
77
78/*
79 * pvsnprintf
80 *
81 * Attempt to format text data under the control of fmt (an sprintf-style
82 * format string) and insert it into buf (which has length len).
83 *
84 * If successful, return the number of bytes emitted, not counting the
85 * trailing zero byte. This will always be strictly less than len.
86 *
87 * If there's not enough space in buf, return an estimate of the buffer size
88 * needed to succeed (this *must* be more than the given len, else callers
89 * might loop infinitely).
90 *
91 * Other error cases do not return, but exit via elog(ERROR) or exit().
92 * Hence, this shouldn't be used inside libpq.
93 *
94 * Caution: callers must be sure to preserve their entry-time errno
95 * when looping, in case the fmt contains "%m".
96 *
97 * Note that the semantics of the return value are not exactly C99's.
98 * First, we don't promise that the estimated buffer size is exactly right;
99 * callers must be prepared to loop multiple times to get the right size.
100 * (Given a C99-compliant vsnprintf, that won't happen, but it is rumored
101 * that some implementations don't always return the same value ...)
102 * Second, we return the recommended buffer size, not one less than that;
103 * this lets overflow concerns be handled here rather than in the callers.
104 */
105size_t
106pvsnprintf(char *buf, size_t len, const char *fmt, va_list args)
107{
108 int nprinted;
109
110 nprinted = vsnprintf(buf, len, fmt, args);
111
112 /* We assume failure means the fmt is bogus, hence hard failure is OK */
113 if (unlikely(nprinted < 0))
114 {
115#ifndef FRONTEND
116 elog(ERROR, "vsnprintf failed: %m with format string \"%s\"", fmt);
117#else
118 fprintf(stderr, "vsnprintf failed: %s with format string \"%s\"\n",
119 strerror(errno), fmt);
120 exit(EXIT_FAILURE);
121#endif
122 }
123
124 if ((size_t) nprinted < len)
125 {
126 /* Success. Note nprinted does not include trailing null. */
127 return (size_t) nprinted;
128 }
129
130 /*
131 * We assume a C99-compliant vsnprintf, so believe its estimate of the
132 * required space, and add one for the trailing null. (If it's wrong, the
133 * logic will still work, but we may loop multiple times.)
134 *
135 * Choke if the required space would exceed MaxAllocSize. Note we use
136 * this palloc-oriented overflow limit even when in frontend.
137 */
138 if (unlikely((size_t) nprinted > MaxAllocSize - 1))
139 {
140#ifndef FRONTEND
141 ereport(ERROR,
142 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
143 errmsg("out of memory")));
144#else
145 fprintf(stderr, _("out of memory\n"));
146 exit(EXIT_FAILURE);
147#endif
148 }
149
150 return nprinted + 1;
151}
152