X-Git-Url: http://git.pk910.de/?p=ircu2.10.12-pk.git;a=blobdiff_plain;f=ircd%2Fdbuf.c;fp=ircd%2Fdbuf.c;h=a7abe5b1a6d85b36c43c778db5cfc51e56b00408;hp=0000000000000000000000000000000000000000;hb=0400a5a6479398d82526785c18c0df8bc8b92dce;hpb=d17e10da972ce5776c60b4c317267c6abe0e1ead diff --git a/ircd/dbuf.c b/ircd/dbuf.c new file mode 100644 index 0000000..a7abe5b --- /dev/null +++ b/ircd/dbuf.c @@ -0,0 +1,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$ + */ +#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 -- Now using assert in ircd_log.h */ +#include + +/* + * 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; +}