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