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