]> git.ipfire.org Git - thirdparty/squid.git/blob - src/MemBuf.cc
2.1 branch merge and gindent
[thirdparty/squid.git] / src / MemBuf.cc
1 /*
2 * $Id: MemBuf.cc,v 1.22 1999/01/19 02:24:19 wessels Exp $
3 *
4 * DEBUG: section 59 auto-growing Memory Buffer with printf
5 * AUTHOR: Alex Rousskov
6 *
7 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
8 * ----------------------------------------------------------
9 *
10 * Squid is the result of efforts by numerous individuals from the
11 * Internet community. Development is led by Duane Wessels of the
12 * National Laboratory for Applied Network Research and funded by the
13 * National Science Foundation. Squid is Copyrighted (C) 1998 by
14 * Duane Wessels and the University of California San Diego. Please
15 * see the COPYRIGHT file for full details. Squid incorporates
16 * software developed and/or copyrighted by other sources. Please see
17 * the CREDITS file for full details.
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.
23 *
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.
28 *
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
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
32 *
33 */
34
35 /*
36 * To-Do: use memory pools for .buf recycling @?@ @?@
37 */
38
39 /*
40 * Rationale:
41 * ----------
42 *
43 * Here is how one would comm_write an object without MemBuffer:
44 *
45 * {
46 * -- allocate:
47 * buf = malloc(big_enough);
48 *
49 * -- "pack":
50 * snprintf object(s) piece-by-piece constantly checking for overflows
51 * and maintaining (buf+offset);
52 * ...
53 *
54 * -- write
55 * comm_write(buf, free, ...);
56 * }
57 *
58 * The whole "packing" idea is quite messy: We are given a buffer of fixed
59 * size and we have to check all the time that we still fit. Sounds logical.
60 *
61 * However, what happens if we have more data? If we are lucky to stop before
62 * we overrun any buffers, we still may have garbage (e.g. half of ETag) in
63 * the buffer.
64 *
65 * MemBuffer:
66 * ----------
67 *
68 * MemBuffer is a memory-resident buffer with printf()-like interface. It
69 * hides all offest handling and overflow checking. Moreover, it has a
70 * build-in control that no partial data has been written.
71 *
72 * MemBuffer is designed to handle relatively small data. It starts with a
73 * small buffer of configurable size to avoid allocating huge buffers all the
74 * time. MemBuffer doubles the buffer when needed. It assert()s that it will
75 * not grow larger than a configurable limit. MemBuffer has virtually no
76 * overhead (and can even reduce memory consumption) compared to old
77 * "packing" approach.
78 *
79 * MemBuffer eliminates both "packing" mess and truncated data:
80 *
81 * {
82 * -- setup
83 * MemBuf buf;
84 *
85 * -- required init with optional size tuning (see #defines for defaults)
86 * memBufInit(&buf, initial-size, absolute-maximum);
87 *
88 * -- "pack" (no need to handle offsets or check for overflows)
89 * memBufPrintf(&buf, ...);
90 * ...
91 *
92 * -- write
93 * comm_write_mbuf(fd, buf, handler, data);
94 *
95 * -- *iff* you did not give the buffer away, free it yourself
96 * -- memBufClean(&buf);
97 * }
98 */
99
100
101 #include "squid.h"
102
103 /* local constants */
104
105 /* default values for buffer sizes, used by memBufDefInit */
106 #define MEM_BUF_INIT_SIZE (2*1024)
107 #define MEM_BUF_MAX_SIZE (2*1000*1024*1024)
108
109
110 /* local routines */
111 static void memBufGrow(MemBuf * mb, mb_size_t min_cap);
112
113
114 /* init with defaults */
115 void
116 memBufDefInit(MemBuf * mb)
117 {
118 memBufInit(mb, MEM_BUF_INIT_SIZE, MEM_BUF_MAX_SIZE);
119 }
120
121
122 /* init with specific sizes */
123 void
124 memBufInit(MemBuf * mb, mb_size_t szInit, mb_size_t szMax)
125 {
126 assert(mb);
127 assert(szInit > 0 && szMax > 0);
128
129 mb->buf = NULL;
130 mb->size = 0;
131 mb->max_capacity = szMax;
132 mb->capacity = 0;
133 mb->freefunc = NULL;
134
135 memBufGrow(mb, szInit);
136 }
137
138 /*
139 * cleans the mb; last function to call if you do not give .buf away with
140 * memBufFreeFunc
141 */
142 void
143 memBufClean(MemBuf * mb)
144 {
145 assert(mb);
146 assert(mb->buf);
147 assert(mb->freefunc); /* not frozen */
148
149 (*mb->freefunc) (mb->buf); /* free */
150 mb->freefunc = NULL; /* freeze */
151 mb->buf = NULL;
152 mb->size = mb->capacity = 0;
153 }
154
155 /* cleans the buffer without changing its capacity
156 * if called with a Null buffer, calls memBufDefInit() */
157 void
158 memBufReset(MemBuf * mb)
159 {
160 assert(mb);
161
162 if (memBufIsNull(mb)) {
163 memBufDefInit(mb);
164 } else {
165 assert(mb->freefunc); /* not frozen */
166 /* reset */
167 memset(mb->buf, 0, mb->capacity);
168 mb->size = 0;
169 }
170 }
171
172 /* unfortunate hack to test if the buffer has been Init()ialized */
173 int
174 memBufIsNull(MemBuf * mb)
175 {
176 assert(mb);
177 if (!mb->buf && !mb->max_capacity && !mb->capacity && !mb->size)
178 return 1; /* is null (not initialized) */
179 assert(mb->buf && mb->max_capacity && mb->capacity); /* paranoid */
180 return 0;
181 }
182
183
184 /* calls memcpy, appends exactly size bytes, extends buffer if needed */
185 void
186 memBufAppend(MemBuf * mb, const char *buf, mb_size_t sz)
187 {
188 assert(mb && buf && sz >= 0);
189 assert(mb->buf);
190 assert(mb->freefunc); /* not frozen */
191
192 if (sz > 0) {
193 if (mb->size + sz > mb->capacity)
194 memBufGrow(mb, mb->size + sz);
195 assert(mb->size + sz <= mb->capacity); /* paranoid */
196 xmemcpy(mb->buf + mb->size, buf, sz);
197 mb->size += sz;
198 }
199 }
200
201 /* calls memBufVPrintf */
202 #if STDC_HEADERS
203 void
204 memBufPrintf(MemBuf * mb, const char *fmt,...)
205 {
206 va_list args;
207 va_start(args, fmt);
208 #else
209 void
210 memBufPrintf(va_alist)
211 va_dcl
212 {
213 va_list args;
214 MemBuf *mb = NULL;
215 const char *fmt = NULL;
216 mb_size_t sz = 0;
217 va_start(args);
218 mb = va_arg(args, MemBuf *);
219 fmt = va_arg(args, char *);
220 #endif
221 memBufVPrintf(mb, fmt, args);
222 va_end(args);
223 }
224
225
226 /* vprintf for other printf()'s to use; calls vsnprintf, extends buf if needed */
227 void
228 memBufVPrintf(MemBuf * mb, const char *fmt, va_list vargs)
229 {
230 int sz = 0;
231 assert(mb && fmt);
232 assert(mb->buf);
233 assert(mb->freefunc); /* not frozen */
234 /* assert in Grow should quit first, but we do not want to have a scary infinite loop */
235 while (mb->capacity <= mb->max_capacity) {
236 mb_size_t free_space = mb->capacity - mb->size;
237 /* put as much as we can */
238 sz = vsnprintf(mb->buf + mb->size, free_space, fmt, vargs);
239 /* check for possible overflow */
240 /* snprintf on Linuz returns -1 on overflows */
241 /* snprintf on FreeBSD returns at least free_space on overflows */
242 if (sz < 0 || sz >= free_space)
243 memBufGrow(mb, mb->capacity + 1);
244 else
245 break;
246 }
247 mb->size += sz;
248 /* on Linux and FreeBSD, '\0' is not counted in return value */
249 /* on XXX it might be counted */
250 /* check that '\0' is appended and not counted */
251 if (!mb->size || mb->buf[mb->size - 1]) {
252 assert(!mb->buf[mb->size]);
253 } else {
254 mb->size--;
255 }
256 }
257
258 /*
259 * returns free() function to be used.
260 * Important:
261 * calling this function "freezes" mb,
262 * do not _update_ mb after that in any way
263 * (you still can read-access .buf and .size)
264 */
265 FREE *
266 memBufFreeFunc(MemBuf * mb)
267 {
268 FREE *ff;
269 assert(mb);
270 assert(mb->buf);
271 assert(mb->freefunc); /* not frozen */
272
273 ff = mb->freefunc;
274 mb->freefunc = NULL; /* freeze */
275 return ff;
276 }
277
278 /* grows (doubles) internal buffer to satisfy required minimal capacity */
279 static void
280 memBufGrow(MemBuf * mb, mb_size_t min_cap)
281 {
282 mb_size_t new_cap;
283 MemBuf old_mb;
284
285 assert(mb);
286 assert(mb->capacity < min_cap);
287
288 /* determine next capacity */
289 new_cap = mb->capacity;
290 if (new_cap > 0)
291 while (new_cap < min_cap)
292 new_cap *= 2; /* double */
293 else
294 new_cap = min_cap;
295
296 /* last chance to fit before we assert(!overflow) */
297 if (new_cap > mb->max_capacity)
298 new_cap = mb->max_capacity;
299
300 assert(new_cap <= mb->max_capacity); /* no overflow */
301 assert(new_cap > mb->capacity); /* progress */
302
303 old_mb = *mb;
304
305 /* allocate new memory */
306 switch (new_cap) {
307 case 2048:
308 mb->buf = memAllocate(MEM_2K_BUF);
309 mb->freefunc = &memFree2K;
310 break;
311 case 4096:
312 mb->buf = memAllocate(MEM_4K_BUF);
313 mb->freefunc = &memFree4K;
314 break;
315 case 8192:
316 mb->buf = memAllocate(MEM_8K_BUF);
317 mb->freefunc = &memFree8K;
318 break;
319 default:
320 /* recycle if old buffer was not "pool"ed */
321 if (old_mb.freefunc == &xfree) {
322 mb->buf = xrealloc(old_mb.buf, new_cap);
323 old_mb.buf = NULL;
324 old_mb.freefunc = NULL;
325 /* init tail, just in case */
326 memset(mb->buf + mb->size, 0, new_cap - mb->size);
327 } else {
328 mb->buf = xcalloc(1, new_cap);
329 mb->freefunc = &xfree;
330 }
331 }
332
333 /* copy and free old buffer if needed */
334 if (old_mb.buf && old_mb.freefunc) {
335 memcpy(mb->buf, old_mb.buf, old_mb.size);
336 (*old_mb.freefunc) (old_mb.buf);
337 } else {
338 assert(!old_mb.buf && !old_mb.freefunc);
339 }
340
341 /* done */
342 mb->capacity = new_cap;
343 }
344
345
346 /* Reports */
347
348 /* puts report on MemBuf _module_ usage into mb */
349 void
350 memBufReport(MemBuf * mb)
351 {
352 assert(mb);
353 memBufPrintf(mb, "memBufReport is not yet implemented @?@\n");
354 }