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