]> git.ipfire.org Git - thirdparty/squid.git/blob - src/MemBuf.cc
Bug 5428: Warn if pkg-config is not found (#1902)
[thirdparty/squid.git] / src / MemBuf.cc
1 /*
2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 /**
10 \verbatim
11 * Rationale:
12 * ----------
13 *
14 * Here is how one would Comm::Write an object without MemBuffer:
15 *
16 * {
17 * -- allocate:
18 * buf = malloc(big_enough);
19 *
20 * -- "pack":
21 * snprintf object(s) piece-by-piece constantly checking for overflows
22 * and maintaining (buf+offset);
23 * ...
24 *
25 * -- write
26 * Comm::Write(buf, free, ...);
27 * }
28 *
29 * The whole "packing" idea is quite messy: We are given a buffer of fixed
30 * size and we have to check all the time that we still fit. Sounds logical.
31 *
32 * However, what happens if we have more data? If we are lucky to stop before
33 * we overrun any buffers, we still may have garbage (e.g. half of ETag) in
34 * the buffer.
35 *
36 * MemBuffer:
37 * ----------
38 *
39 * MemBuffer is a memory-resident buffer with printf()-like interface. It
40 * hides all offset handling and overflow checking. Moreover, it has a
41 * built-in control that no partial data has been written.
42 *
43 * MemBuffer is designed to handle relatively small data. It starts with a
44 * small buffer of configurable size to avoid allocating huge buffers all the
45 * time. MemBuffer doubles the buffer when needed. It assert()s that it will
46 * not grow larger than a configurable limit. MemBuffer has virtually no
47 * overhead (and can even reduce memory consumption) compared to old
48 * "packing" approach.
49 *
50 * MemBuffer eliminates both "packing" mess and truncated data:
51 *
52 * {
53 * -- setup
54 * MemBuf buf;
55 *
56 * -- required init with optional size tuning (see #defines for defaults)
57 * buf.init(initial-size, absolute-maximum);
58 *
59 * -- "pack" (no need to handle offsets or check for overflows)
60 * buf.Printf(...);
61 * ...
62 *
63 * -- write
64 * Comm::Write(fd, buf, callback);
65 *
66 * -- *iff* you did not give the buffer away, free it yourself
67 * -- buf.clean();
68 * }
69 \endverbatim
70 */
71
72 #include "squid.h"
73 #include "mem/forward.h"
74 #include "MemBuf.h"
75
76 /* local constants */
77
78 /* default values for buffer sizes, used by memBufDefInit */
79 #define MEM_BUF_INIT_SIZE (2*1024)
80 #define MEM_BUF_MAX_SIZE (2*1000*1024*1024)
81
82 CBDATA_CLASS_INIT(MemBuf);
83
84 /** init with defaults */
85 void
86 MemBuf::init()
87 {
88 init(MEM_BUF_INIT_SIZE, MEM_BUF_MAX_SIZE);
89 }
90
91 /** init with specific sizes */
92 void
93 MemBuf::init(mb_size_t szInit, mb_size_t szMax)
94 {
95 assert(szInit > 0 && szMax > 0);
96 buf = nullptr;
97 size = 0;
98 max_capacity = szMax;
99 capacity = 0;
100 stolen = 0;
101 grow(szInit);
102 terminate();
103 }
104
105 /**
106 * cleans the mb; last function to call if you do not give .buf away with
107 * memBufFreeFunc
108 */
109 void
110 MemBuf::clean()
111 {
112 if (isNull()) {
113 // nothing to do
114 } else {
115 assert(buf);
116 assert(!stolen); /* not frozen */
117
118 memFreeBuf(capacity, buf);
119 buf = nullptr;
120 size = capacity = max_capacity = 0;
121 }
122 }
123
124 /**
125 * Cleans the buffer without changing its capacity
126 * if called with a Null buffer, calls memBufDefInit()
127 */
128 void
129 MemBuf::reset()
130 {
131 if (isNull()) {
132 init();
133 } else {
134 assert(!stolen); /* not frozen */
135 /* reset */
136 memset(buf, 0, capacity);
137 size = 0;
138 }
139 }
140
141 /**
142 * Unfortunate hack to test if the buffer has been Init()ialized
143 */
144 int
145 MemBuf::isNull() const
146 {
147 if (!buf && !max_capacity && !capacity && !size)
148 return 1; /* is null (not initialized) */
149
150 assert(buf && max_capacity && capacity); /* paranoid */
151
152 return 0;
153 }
154
155 mb_size_t MemBuf::spaceSize() const
156 {
157 const mb_size_t terminatedSize = size + 1;
158 return (terminatedSize < capacity) ? capacity - terminatedSize : 0;
159 }
160
161 mb_size_t MemBuf::potentialSpaceSize() const
162 {
163 const mb_size_t terminatedSize = size + 1;
164 return (terminatedSize < max_capacity) ? max_capacity - terminatedSize : 0;
165 }
166
167 /// removes sz bytes and "packs" by moving content left
168 void MemBuf::consume(mb_size_t shiftSize)
169 {
170 const mb_size_t cSize = contentSize();
171 assert(0 <= shiftSize && shiftSize <= cSize);
172 assert(!stolen); /* not frozen */
173
174 if (shiftSize > 0) {
175 if (shiftSize < cSize)
176 memmove(buf, buf + shiftSize, cSize - shiftSize);
177
178 size -= shiftSize;
179
180 terminate();
181 }
182 }
183
184 /// removes all whitespace prefix bytes and "packs" by moving content left
185 void MemBuf::consumeWhitespacePrefix()
186 {
187 if (contentSize() > 0) {
188 const char *end = buf + contentSize();
189 const char *p = buf;
190 for (; p<end && xisspace(*p); ++p);
191 if (p-buf > 0)
192 consume(p-buf);
193 }
194 }
195
196 // removes last tailSize bytes
197 void MemBuf::truncate(mb_size_t tailSize)
198 {
199 const mb_size_t cSize = contentSize();
200 assert(0 <= tailSize && tailSize <= cSize);
201 assert(!stolen); /* not frozen */
202 size -= tailSize;
203 }
204
205 /**
206 * calls memcpy, appends exactly size bytes,
207 * extends buffer or creates buffer if needed.
208 */
209 void MemBuf::append(const char *newContent, int sz)
210 {
211 assert(sz >= 0);
212 assert(buf || (0==capacity && 0==size));
213 assert(!stolen); /* not frozen */
214
215 if (sz > 0) {
216 if (size + sz + 1 > capacity)
217 grow(size + sz + 1);
218
219 assert(size + sz <= capacity); /* paranoid */
220 memcpy(space(), newContent, sz);
221 appended(sz);
222 }
223 }
224
225 /// updates content size after external append
226 void MemBuf::appended(mb_size_t sz)
227 {
228 assert(size + sz <= capacity);
229 size += sz;
230 terminate();
231 }
232
233 /**
234 * Null-terminate in case we are used as a string.
235 * Extra octet is not counted in the content size (or space size)
236 *
237 \note XXX: but the extra octet is counted when growth decisions are made!
238 * This will cause the buffer to grow when spaceSize() == 1 on append,
239 * which will assert() if the buffer cannot grow any more.
240 */
241 void MemBuf::terminate()
242 {
243 assert(size < capacity);
244 *space() = '\0';
245 }
246
247 /**
248 * vappendf for other printf()'s to use; calls vsnprintf, extends buf if needed
249 */
250 void
251 MemBuf::vappendf(const char *fmt, va_list vargs)
252 {
253 int sz = 0;
254 assert(fmt);
255 assert(buf);
256 assert(!stolen); /* not frozen */
257 /* assert in Grow should quit first, but we do not want to have a scary infinite loop */
258
259 while (capacity <= max_capacity) {
260 mb_size_t free_space = capacity - size;
261 /* put as much as we can */
262
263 /* Fix of bug 753r. The value of vargs is undefined
264 * after vsnprintf() returns. Make a copy of vargs
265 * in case we loop around and call vsnprintf() again.
266 */
267 va_list ap;
268 va_copy(ap,vargs);
269 sz = vsnprintf(buf + size, free_space, fmt, ap);
270 va_end(ap);
271
272 /* check for possible overflow */
273 /* snprintf on Linuz returns -1 on overflows */
274 /* snprintf on FreeBSD returns at least free_space on overflows */
275
276 if (sz < 0 || sz >= free_space)
277 grow(capacity + 1);
278 else
279 break;
280 }
281
282 size += sz;
283 /* on Linux and FreeBSD, '\0' is not counted in return value */
284 /* on XXX it might be counted */
285 /* check that '\0' is appended and not counted */
286
287 if (!size || buf[size - 1]) {
288 assert(!buf[size]);
289 } else {
290 --size;
291 }
292 }
293
294 /**
295 * Important:
296 * calling this function "freezes" mb,
297 * do not _update_ mb after that in any way
298 * (you still can read-access .buf and .size)
299 *
300 \retval free() function to be used.
301 */
302 FREE *
303 MemBuf::freeFunc()
304 {
305 FREE *ff;
306 assert(buf);
307 assert(!stolen); /* not frozen */
308
309 ff = memFreeBufFunc((size_t) capacity);
310 stolen = 1; /* freeze */
311 return ff;
312 }
313
314 /**
315 * Grows (doubles) internal buffer to satisfy required minimal capacity
316 */
317 void
318 MemBuf::grow(mb_size_t min_cap)
319 {
320 size_t new_cap;
321 size_t buf_cap;
322
323 assert(!stolen);
324 assert(capacity < min_cap);
325
326 /* determine next capacity */
327
328 if (min_cap > 64 * 1024) {
329 new_cap = 64 * 1024;
330
331 while (new_cap < (size_t) min_cap)
332 new_cap += 64 * 1024; /* increase in reasonable steps */
333 } else {
334 new_cap = (size_t) min_cap;
335 }
336
337 /* last chance to fit before we assert(!overflow) */
338 if (new_cap > (size_t) max_capacity)
339 new_cap = (size_t) max_capacity;
340
341 assert(new_cap <= (size_t) max_capacity); /* no overflow */
342
343 assert(new_cap > (size_t) capacity); /* progress */
344
345 buf_cap = (size_t) capacity;
346
347 buf = (char *)memReallocBuf(buf, new_cap, &buf_cap);
348
349 /* done */
350 capacity = (mb_size_t) buf_cap;
351 }
352
353 /* Reports */
354
355 /**
356 * Puts report on MemBuf _module_ usage into mb
357 */
358 void
359 memBufReport(MemBuf * mb)
360 {
361 assert(mb);
362 mb->appendf("memBufReport is not yet implemented @?@\n");
363 }
364