/* * 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 #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; }