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