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