]> git.ipfire.org Git - thirdparty/squid.git/blob - src/MemBuf.cc
MemBuf fix: empty MemBufs are not NULL terminated
[thirdparty/squid.git] / src / MemBuf.cc
1 /*
2 * DEBUG: section 59 auto-growing Memory Buffer with printf
3 * AUTHOR: Alex Rousskov
4 *
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
7 *
8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
16 *
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
30 *
31 */
32
33 /**
34 \todo use memory pools for .buf recycling @?@ @?@
35 */
36
37 /**
38 \verbatim
39 * Rationale:
40 * ----------
41 *
42 * Here is how one would Comm::Write an object without MemBuffer:
43 *
44 * {
45 * -- allocate:
46 * buf = malloc(big_enough);
47 *
48 * -- "pack":
49 * snprintf object(s) piece-by-piece constantly checking for overflows
50 * and maintaining (buf+offset);
51 * ...
52 *
53 * -- write
54 * Comm::Write(buf, free, ...);
55 * }
56 *
57 * The whole "packing" idea is quite messy: We are given a buffer of fixed
58 * size and we have to check all the time that we still fit. Sounds logical.
59 *
60 * However, what happens if we have more data? If we are lucky to stop before
61 * we overrun any buffers, we still may have garbage (e.g. half of ETag) in
62 * the buffer.
63 *
64 * MemBuffer:
65 * ----------
66 *
67 * MemBuffer is a memory-resident buffer with printf()-like interface. It
68 * hides all offest handling and overflow checking. Moreover, it has a
69 * build-in control that no partial data has been written.
70 *
71 * MemBuffer is designed to handle relatively small data. It starts with a
72 * small buffer of configurable size to avoid allocating huge buffers all the
73 * time. MemBuffer doubles the buffer when needed. It assert()s that it will
74 * not grow larger than a configurable limit. MemBuffer has virtually no
75 * overhead (and can even reduce memory consumption) compared to old
76 * "packing" approach.
77 *
78 * MemBuffer eliminates both "packing" mess and truncated data:
79 *
80 * {
81 * -- setup
82 * MemBuf buf;
83 *
84 * -- required init with optional size tuning (see #defines for defaults)
85 * buf.init(initial-size, absolute-maximum);
86 *
87 * -- "pack" (no need to handle offsets or check for overflows)
88 * buf.Printf(...);
89 * ...
90 *
91 * -- write
92 * Comm::Write(fd, buf, callback);
93 *
94 * -- *iff* you did not give the buffer away, free it yourself
95 * -- buf.clean();
96 * }
97 \endverbatim
98 */
99
100 #include "squid.h"
101 #include "Mem.h"
102 #include "MemBuf.h"
103 #include "profiler/Profiler.h"
104
105 #ifdef VA_COPY
106 #undef VA_COPY
107 #endif
108 #if defined HAVE_VA_COPY
109 #define VA_COPY va_copy
110 #elif defined HAVE___VA_COPY
111 #define VA_COPY __va_copy
112 #endif
113
114 /* local constants */
115
116 /* default values for buffer sizes, used by memBufDefInit */
117 #define MEM_BUF_INIT_SIZE (2*1024)
118 #define MEM_BUF_MAX_SIZE (2*1000*1024*1024)
119
120 CBDATA_CLASS_INIT(MemBuf);
121
122 /** init with defaults */
123 void
124 MemBuf::init()
125 {
126 init(MEM_BUF_INIT_SIZE, MEM_BUF_MAX_SIZE);
127 }
128
129 /** init with specific sizes */
130 void
131 MemBuf::init(mb_size_t szInit, mb_size_t szMax)
132 {
133 assert(szInit > 0 && szMax > 0);
134 buf = NULL;
135 size = 0;
136 max_capacity = szMax;
137 capacity = 0;
138 stolen = 0;
139 grow(szInit);
140 terminate();
141 }
142
143 /**
144 * cleans the mb; last function to call if you do not give .buf away with
145 * memBufFreeFunc
146 */
147 void
148 MemBuf::clean()
149 {
150 if (isNull()) {
151 // nothing to do
152 } else {
153 assert(buf);
154 assert(!stolen); /* not frozen */
155
156 memFreeBuf(capacity, buf);
157 buf = NULL;
158 size = capacity = max_capacity = 0;
159 }
160 }
161
162 /**
163 * Cleans the buffer without changing its capacity
164 * if called with a Null buffer, calls memBufDefInit()
165 */
166 void
167 MemBuf::reset()
168 {
169 if (isNull()) {
170 init();
171 } else {
172 assert(!stolen); /* not frozen */
173 /* reset */
174 memset(buf, 0, capacity);
175 size = 0;
176 }
177 }
178
179 /**
180 * Unfortunate hack to test if the buffer has been Init()ialized
181 */
182 int
183 MemBuf::isNull()
184 {
185 if (!buf && !max_capacity && !capacity && !size)
186 return 1; /* is null (not initialized) */
187
188 assert(buf && max_capacity && capacity); /* paranoid */
189
190 return 0;
191 }
192
193 mb_size_t MemBuf::spaceSize() const
194 {
195 const mb_size_t terminatedSize = size + 1;
196 return (terminatedSize < capacity) ? capacity - terminatedSize : 0;
197 }
198
199 mb_size_t MemBuf::potentialSpaceSize() const
200 {
201 const mb_size_t terminatedSize = size + 1;
202 return (terminatedSize < max_capacity) ? max_capacity - terminatedSize : 0;
203 }
204
205 /// removes sz bytes and "packs" by moving content left
206 void MemBuf::consume(mb_size_t shiftSize)
207 {
208 const mb_size_t cSize = contentSize();
209 assert(0 <= shiftSize && shiftSize <= cSize);
210 assert(!stolen); /* not frozen */
211
212 PROF_start(MemBuf_consume);
213 if (shiftSize > 0) {
214 if (shiftSize < cSize)
215 memmove(buf, buf + shiftSize, cSize - shiftSize);
216
217 size -= shiftSize;
218
219 terminate();
220 }
221 PROF_stop(MemBuf_consume);
222 }
223
224 /// removes all whitespace prefix bytes and "packs" by moving content left
225 void MemBuf::consumeWhitespacePrefix()
226 {
227 PROF_start(MemBuf_consumeWhitespace);
228 if (contentSize() > 0) {
229 const char *end = buf + contentSize();
230 const char *p = buf;
231 for (; p<end && xisspace(*p); ++p);
232 if (p-buf > 0)
233 consume(p-buf);
234 }
235 PROF_stop(MemBuf_consumeWhitespace);
236 }
237
238 // removes last tailSize bytes
239 void MemBuf::truncate(mb_size_t tailSize)
240 {
241 const mb_size_t cSize = contentSize();
242 assert(0 <= tailSize && tailSize <= cSize);
243 assert(!stolen); /* not frozen */
244 size -= tailSize;
245 }
246
247 /**
248 * calls memcpy, appends exactly size bytes,
249 * extends buffer or creates buffer if needed.
250 */
251 void MemBuf::append(const char *newContent, mb_size_t sz)
252 {
253 assert(sz >= 0);
254 assert(buf || (0==capacity && 0==size));
255 assert(!stolen); /* not frozen */
256
257 PROF_start(MemBuf_append);
258 if (sz > 0) {
259 if (size + sz + 1 > capacity)
260 grow(size + sz + 1);
261
262 assert(size + sz <= capacity); /* paranoid */
263 memcpy(space(), newContent, sz);
264 appended(sz);
265 }
266 PROF_stop(MemBuf_append);
267 }
268
269 /// updates content size after external append
270 void MemBuf::appended(mb_size_t sz)
271 {
272 assert(size + sz <= capacity);
273 size += sz;
274 terminate();
275 }
276
277 /**
278 * Null-terminate in case we are used as a string.
279 * Extra octet is not counted in the content size (or space size)
280 *
281 \note XXX: but the extra octet is counted when growth decisions are made!
282 * This will cause the buffer to grow when spaceSize() == 1 on append,
283 * which will assert() if the buffer cannot grow any more.
284 */
285 void MemBuf::terminate()
286 {
287 assert(size < capacity);
288 *space() = '\0';
289 }
290
291 /* calls memBufVPrintf */
292 void
293 MemBuf::Printf(const char *fmt,...)
294 {
295 va_list args;
296 va_start(args, fmt);
297 vPrintf(fmt, args);
298 va_end(args);
299 }
300
301 /**
302 * vPrintf for other printf()'s to use; calls vsnprintf, extends buf if needed
303 */
304 void
305 MemBuf::vPrintf(const char *fmt, va_list vargs)
306 {
307 #ifdef VA_COPY
308 va_list ap;
309 #endif
310
311 int sz = 0;
312 assert(fmt);
313 assert(buf);
314 assert(!stolen); /* not frozen */
315 /* assert in Grow should quit first, but we do not want to have a scary infinite loop */
316
317 while (capacity <= max_capacity) {
318 mb_size_t free_space = capacity - size;
319 /* put as much as we can */
320
321 #ifdef VA_COPY
322 /* Fix of bug 753r. The value of vargs is undefined
323 * after vsnprintf() returns. Make a copy of vargs
324 * incase we loop around and call vsnprintf() again.
325 */
326 VA_COPY(ap,vargs);
327 sz = vsnprintf(buf + size, free_space, fmt, ap);
328 va_end(ap);
329 #else /* VA_COPY */
330
331 sz = vsnprintf(buf + size, free_space, fmt, vargs);
332 #endif /*VA_COPY*/
333 /* check for possible overflow */
334 /* snprintf on Linuz returns -1 on overflows */
335 /* snprintf on FreeBSD returns at least free_space on overflows */
336
337 if (sz < 0 || sz >= free_space)
338 grow(capacity + 1);
339 else
340 break;
341 }
342
343 size += sz;
344 /* on Linux and FreeBSD, '\0' is not counted in return value */
345 /* on XXX it might be counted */
346 /* check that '\0' is appended and not counted */
347
348 if (!size || buf[size - 1]) {
349 assert(!buf[size]);
350 } else {
351 --size;
352 }
353 }
354
355 /**
356 * Important:
357 * calling this function "freezes" mb,
358 * do not _update_ mb after that in any way
359 * (you still can read-access .buf and .size)
360 *
361 \retval free() function to be used.
362 */
363 FREE *
364 MemBuf::freeFunc()
365 {
366 FREE *ff;
367 assert(buf);
368 assert(!stolen); /* not frozen */
369
370 ff = memFreeBufFunc((size_t) capacity);
371 stolen = 1; /* freeze */
372 return ff;
373 }
374
375 /**
376 * Grows (doubles) internal buffer to satisfy required minimal capacity
377 */
378 void
379 MemBuf::grow(mb_size_t min_cap)
380 {
381 size_t new_cap;
382 size_t buf_cap;
383
384 assert(!stolen);
385 assert(capacity < min_cap);
386
387 PROF_start(MemBuf_grow);
388
389 /* determine next capacity */
390
391 if (min_cap > 64 * 1024) {
392 new_cap = 64 * 1024;
393
394 while (new_cap < (size_t) min_cap)
395 new_cap += 64 * 1024; /* increase in reasonable steps */
396 } else {
397 new_cap = (size_t) min_cap;
398 }
399
400 /* last chance to fit before we assert(!overflow) */
401 if (new_cap > (size_t) max_capacity)
402 new_cap = (size_t) max_capacity;
403
404 assert(new_cap <= (size_t) max_capacity); /* no overflow */
405
406 assert(new_cap > (size_t) capacity); /* progress */
407
408 buf_cap = (size_t) capacity;
409
410 buf = (char *)memReallocBuf(buf, new_cap, &buf_cap);
411
412 /* done */
413 capacity = (mb_size_t) buf_cap;
414 PROF_stop(MemBuf_grow);
415 }
416
417 /* Reports */
418
419 /**
420 * Puts report on MemBuf _module_ usage into mb
421 */
422 void
423 memBufReport(MemBuf * mb)
424 {
425 assert(mb);
426 mb->Printf("memBufReport is not yet implemented @?@\n");
427 }
428
429 #if !_USE_INLINE_
430 #include "MemBuf.cci"
431 #endif