/*
* IRC - Internet Relay Chat, ircd/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 1099 2005-06-22 18:03:53Z sirvulcan $
*/
#include "config.h"
#include "dbuf.h"
#include "ircd_alloc.h"
#include "ircd_chattr.h"
#include "ircd_features.h"
#include "send.h"
#include "sys.h" /* MIN */
#include <assert.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;
}
syntax highlighted by Code2HTML, v. 0.9.1