1 |
/* |
2 |
* ircd-hybrid: an advanced Internet Relay Chat Daemon(ircd). |
3 |
* |
4 |
* Copyright (C) 1990 Markku Savela |
5 |
* Copyright (C) 2013 by the Hybrid Development Team. |
6 |
* |
7 |
* This program is free software; you can redistribute it and/or modify |
8 |
* it under the terms of the GNU General Public License as published by |
9 |
* the Free Software Foundation; either version 2 of the License, or |
10 |
* (at your option) any later version. |
11 |
* |
12 |
* This program is distributed in the hope that it will be useful, |
13 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 |
* GNU General Public License for more details. |
16 |
* |
17 |
* You should have received a copy of the GNU General Public License |
18 |
* along with this program; if not, write to the Free Software |
19 |
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
20 |
* USA |
21 |
*/ |
22 |
|
23 |
/*! \file dbuf.c |
24 |
* \brief Interfaces and declarations for dealing with data buffers. |
25 |
* \version $Id$ |
26 |
*/ |
27 |
|
28 |
#include "stdinc.h" |
29 |
#include "dbuf.h" |
30 |
#include "memory.h" |
31 |
#include "log.h" |
32 |
#include "send.h" |
33 |
#include "irc_string.h" |
34 |
|
35 |
#define FEAT_BUFFERPOOL 27000000 /* XXX */ |
36 |
|
37 |
/* |
38 |
* dbuf is a collection of functions which can be used to |
39 |
* maintain a dynamic buffering of a byte stream. |
40 |
* Functions allocate and release memory dynamically as |
41 |
* required [Actually, there is nothing that prevents |
42 |
* this package maintaining the buffer on disk, either] |
43 |
*/ |
44 |
|
45 |
/** Number of dbufs allocated. |
46 |
* This should only be modified by dbuf.c. |
47 |
*/ |
48 |
int DBufAllocCount = 0; |
49 |
/** Number of dbufs in use. |
50 |
* This should only be modified by dbuf.c. |
51 |
*/ |
52 |
int DBufUsedCount = 0; |
53 |
|
54 |
/** List of allocated but unused DBuf structures. */ |
55 |
static struct DBufBuffer *dbufFreeList; |
56 |
|
57 |
/** Size of data for a single DBufBuffer. */ |
58 |
#define DBUF_SIZE 2048 |
59 |
|
60 |
/** Single data buffer in a DBuf. */ |
61 |
struct DBufBuffer |
62 |
{ |
63 |
struct DBufBuffer *next; /**< Next data buffer, NULL if last */ |
64 |
char *start; /**< Data starts here */ |
65 |
char *end; /**< Data ends here */ |
66 |
char data[DBUF_SIZE]; /**< Actual data stored here */ |
67 |
}; |
68 |
|
69 |
/** Return memory used by allocated data buffers. |
70 |
* @param[out] allocated Receives number of bytes allocated to DBufs. |
71 |
* @param[out] used Receives number of bytes for currently used DBufs. |
72 |
*/ |
73 |
void |
74 |
dbuf_count_memory(size_t *allocated, size_t *used) |
75 |
{ |
76 |
assert(allocated); |
77 |
assert(used); |
78 |
|
79 |
*allocated = DBufAllocCount * sizeof(struct DBufBuffer); |
80 |
*used = DBufUsedCount * sizeof(struct DBufBuffer); |
81 |
} |
82 |
|
83 |
/** Allocate a new DBufBuffer. |
84 |
* If #dbufFreeList != NULL, use the head of that list; otherwise, |
85 |
* allocate a new buffer. |
86 |
* @return Newly allocated buffer list. |
87 |
*/ |
88 |
static struct DBufBuffer * |
89 |
dbuf_alloc(void) |
90 |
{ |
91 |
struct DBufBuffer *db = dbufFreeList; |
92 |
|
93 |
if (db) |
94 |
{ |
95 |
dbufFreeList = db->next; |
96 |
++DBufUsedCount; |
97 |
} |
98 |
else if ((DBufAllocCount * DBUF_SIZE) < FEAT_BUFFERPOOL) |
99 |
{ |
100 |
db = (struct DBufBuffer *)MyMalloc(sizeof(struct DBufBuffer)); |
101 |
assert(0 != db); |
102 |
++DBufAllocCount; |
103 |
++DBufUsedCount; |
104 |
} |
105 |
|
106 |
return db; |
107 |
} |
108 |
|
109 |
/** Release a DBufBuffer back to the free list. |
110 |
* @param[in] db Data buffer to release. |
111 |
*/ |
112 |
static void |
113 |
dbuf_free(struct DBufBuffer *db) |
114 |
{ |
115 |
assert(db); |
116 |
|
117 |
--DBufUsedCount; |
118 |
db->next = dbufFreeList; |
119 |
dbufFreeList = db; |
120 |
} |
121 |
|
122 |
/** Handle a memory allocation error on a DBuf. |
123 |
* This frees all the buffers owned by the DBuf, since we have to |
124 |
* close the associated connection. |
125 |
* @param[in] dyn DBuf to clean out. |
126 |
* @return Zero. |
127 |
*/ |
128 |
static int |
129 |
dbuf_malloc_error(struct DBuf *dyn) |
130 |
{ |
131 |
struct DBufBuffer *db = NULL, *next = NULL; |
132 |
|
133 |
for (db = dyn->head; db; db = next) |
134 |
{ |
135 |
next = db->next; |
136 |
dbuf_free(db); |
137 |
} |
138 |
|
139 |
dyn->tail = dyn->head = 0; |
140 |
dyn->length = 0; |
141 |
return 0; |
142 |
} |
143 |
|
144 |
/** Append bytes to a data buffer. |
145 |
* @param[in] dyn Buffer to append to. |
146 |
* @param[in] buf Data to append. |
147 |
* @param[in] length Number of bytes to append. |
148 |
* @return Non-zero on success, or zero on failure. |
149 |
*/ |
150 |
int |
151 |
dbuf_put(struct DBuf *dyn, const char *buf, unsigned int length) |
152 |
{ |
153 |
struct DBufBuffer **h; |
154 |
struct DBufBuffer *db; |
155 |
unsigned int chunk; |
156 |
|
157 |
assert(dyn); |
158 |
assert(buf); |
159 |
|
160 |
/* |
161 |
* Locate the last non-empty buffer. If the last buffer is full, |
162 |
* the loop will terminate with 'db==NULL'. |
163 |
* This loop assumes that the 'dyn->length' field is correctly |
164 |
* maintained, as it should--no other check really needed. |
165 |
*/ |
166 |
if (!dyn->length) |
167 |
h = &(dyn->head); |
168 |
else |
169 |
h = &(dyn->tail); |
170 |
|
171 |
/* |
172 |
* Append users data to buffer, allocating buffers as needed |
173 |
*/ |
174 |
dyn->length += length; |
175 |
|
176 |
for (; length > 0; h = &(db->next)) |
177 |
{ |
178 |
if (!(db = *h)) |
179 |
{ |
180 |
if (!(db = dbuf_alloc())) |
181 |
{ |
182 |
/* |
183 |
* From "Married With Children" episode were Al bought a REAL toilet |
184 |
* on the black market because he was tired of the wimpy water |
185 |
* conserving toilets they make these days --Bleep |
186 |
*/ |
187 |
|
188 |
/* |
189 |
* Apparently this doesn't work, the server _has_ to |
190 |
* dump a few clients to handle the load. A fully loaded |
191 |
* server cannot handle a net break without dumping some |
192 |
* clients. If we flush the connections here under a full |
193 |
* load we may end up starving the kernel for mbufs and |
194 |
* crash the machine |
195 |
*/ |
196 |
|
197 |
/* |
198 |
* Attempt to recover from buffer starvation before |
199 |
* bailing this may help servers running out of memory |
200 |
*/ |
201 |
flush_connections(0); |
202 |
db = dbuf_alloc(); |
203 |
|
204 |
if (!db) |
205 |
return dbuf_malloc_error(dyn); |
206 |
} |
207 |
|
208 |
dyn->tail = db; |
209 |
*h = db; |
210 |
db->next = 0; |
211 |
db->start = db->end = db->data; |
212 |
} |
213 |
|
214 |
chunk = (db->data + DBUF_SIZE) - db->end; |
215 |
|
216 |
if (chunk) |
217 |
{ |
218 |
if (chunk > length) |
219 |
chunk = length; |
220 |
|
221 |
memcpy(db->end, buf, chunk); |
222 |
|
223 |
length -= chunk; |
224 |
buf += chunk; |
225 |
db->end += chunk; |
226 |
} |
227 |
} |
228 |
|
229 |
return 1; |
230 |
} |
231 |
|
232 |
/** Get the first contiguous block of data from a DBuf. |
233 |
* Generally a call to dbuf_map(dyn, &count) will be followed with a |
234 |
* call to dbuf_delete(dyn, count). |
235 |
* @param[in] dyn DBuf to retrieve data from. |
236 |
* @param[out] length Receives number of bytes in block. |
237 |
* @return Pointer to start of block (or NULL if the first block is empty). |
238 |
*/ |
239 |
const char * |
240 |
dbuf_map(const struct DBuf *dyn, unsigned int *length) |
241 |
{ |
242 |
assert(dyn); |
243 |
assert(length); |
244 |
|
245 |
if (!dyn->length) |
246 |
{ |
247 |
*length = 0; |
248 |
return 0; |
249 |
} |
250 |
|
251 |
assert(dyn->head); |
252 |
|
253 |
*length = dyn->head->end - dyn->head->start; |
254 |
return dyn->head->start; |
255 |
} |
256 |
|
257 |
/** Discard data from a DBuf. |
258 |
* @param[in,out] dyn DBuf to drop data from. |
259 |
* @param[in] length Number of bytes to discard. |
260 |
*/ |
261 |
void |
262 |
dbuf_delete(struct DBuf *dyn, unsigned int length) |
263 |
{ |
264 |
struct DBufBuffer *db = NULL; |
265 |
unsigned int chunk = 0; |
266 |
|
267 |
if (length > dyn->length) |
268 |
length = dyn->length; |
269 |
|
270 |
while (length > 0) |
271 |
{ |
272 |
if (!(db = dyn->head)) |
273 |
break; |
274 |
|
275 |
chunk = db->end - db->start; |
276 |
if (chunk > length) |
277 |
chunk = length; |
278 |
|
279 |
length -= chunk; |
280 |
dyn->length -= chunk; |
281 |
db->start += chunk; |
282 |
|
283 |
if (db->start == db->end) |
284 |
{ |
285 |
dyn->head = db->next; |
286 |
dbuf_free(db); |
287 |
} |
288 |
} |
289 |
|
290 |
if (!dyn->head) |
291 |
{ |
292 |
dyn->length = 0; |
293 |
dyn->tail = 0; |
294 |
} |
295 |
} |
296 |
|
297 |
/** Copy data from a buffer and remove what was copied. |
298 |
* @param[in,out] dyn Buffer to copy from. |
299 |
* @param[out] buf Buffer to write to. |
300 |
* @param[in] length Maximum number of bytes to copy. |
301 |
* @return Number of bytes actually copied. |
302 |
*/ |
303 |
unsigned int |
304 |
dbuf_get(struct DBuf *dyn, char *buf, unsigned int length) |
305 |
{ |
306 |
unsigned int moved = 0; |
307 |
unsigned int chunk; |
308 |
const char *b; |
309 |
|
310 |
assert(dyn); |
311 |
assert(buf); |
312 |
|
313 |
while (length > 0 && (b = dbuf_map(dyn, &chunk))) |
314 |
{ |
315 |
if (chunk > length) |
316 |
chunk = length; |
317 |
|
318 |
memcpy(buf, b, chunk); |
319 |
dbuf_delete(dyn, chunk); |
320 |
|
321 |
buf += chunk; |
322 |
length -= chunk; |
323 |
moved += chunk; |
324 |
} |
325 |
|
326 |
return moved; |
327 |
} |
328 |
|
329 |
/** Flush empty lines from a buffer. |
330 |
* @param[in,out] dyn Data buffer to flush. |
331 |
* @return Number of bytes in first available block (or zero if none). |
332 |
*/ |
333 |
static unsigned int |
334 |
dbuf_flush(struct DBuf *dyn) |
335 |
{ |
336 |
struct DBufBuffer *db = dyn->head; |
337 |
|
338 |
if (!db) |
339 |
return 0; |
340 |
|
341 |
assert(db->start < db->end); |
342 |
|
343 |
/* |
344 |
* Flush extra line terms |
345 |
*/ |
346 |
while (IsEol(*db->start)) |
347 |
{ |
348 |
if (++db->start == db->end) |
349 |
{ |
350 |
dyn->head = db->next; |
351 |
dbuf_free(db); |
352 |
|
353 |
if (!(db = dyn->head)) |
354 |
{ |
355 |
dyn->tail = 0; |
356 |
dyn->length = 0; |
357 |
break; |
358 |
} |
359 |
} |
360 |
|
361 |
--dyn->length; |
362 |
} |
363 |
|
364 |
return dyn->length; |
365 |
} |
366 |
|
367 |
/** Copy a single line from a data buffer. |
368 |
* If the output buffer cannot hold the whole line, or if there is no |
369 |
* EOL in the buffer, return 0. |
370 |
* @param[in,out] dyn Data buffer to copy from. |
371 |
* @param[out] buf Buffer to copy to. |
372 |
* @param[in] length Maximum number of bytes to copy. |
373 |
* @return Number of bytes copied to \a buf. |
374 |
*/ |
375 |
unsigned int |
376 |
dbuf_getmsg(struct DBuf *dyn, char *buf, unsigned int length) |
377 |
{ |
378 |
struct DBufBuffer *db = NULL; |
379 |
char *start = NULL; |
380 |
char *end = NULL; |
381 |
unsigned int count = 0; |
382 |
unsigned int copied = 0; |
383 |
|
384 |
assert(dyn); |
385 |
assert(buf); |
386 |
|
387 |
if (!dbuf_flush(dyn)) |
388 |
return 0; |
389 |
|
390 |
assert(dyn->head); |
391 |
|
392 |
db = dyn->head; |
393 |
start = db->start; |
394 |
|
395 |
assert(start < db->end); |
396 |
|
397 |
if (length > dyn->length) |
398 |
length = dyn->length; |
399 |
|
400 |
/* |
401 |
* Might as well copy it while we're here |
402 |
*/ |
403 |
while (length > 0) |
404 |
{ |
405 |
end = IRCD_MIN(db->end, (start + length)); |
406 |
|
407 |
while (start < end && !IsEol(*start)) |
408 |
*buf++ = *start++; |
409 |
|
410 |
count = start - db->start; |
411 |
|
412 |
if (start < end) |
413 |
{ |
414 |
*buf = '\0'; |
415 |
copied += count; |
416 |
dbuf_delete(dyn, copied); |
417 |
dbuf_flush(dyn); |
418 |
return copied; |
419 |
} |
420 |
|
421 |
if (!(db = db->next)) |
422 |
break; |
423 |
copied += count; |
424 |
length -= count; |
425 |
start = db->start; |
426 |
} |
427 |
|
428 |
return 0; |
429 |
} |