/************************************************************************
 *   IRC - Internet Relay Chat, src/sbuf.c
 *   Copyright (C) 2004 David Parton
 *
 *   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.
 *
 * $Id: sbuf.c,v 1.3 2005/07/05 03:17:54 sheik Exp $
 */

#include "sbuf.h"
#include "struct.h"
#include "common.h"
#include "sys.h"
#include "h.h"
#include "memcount.h"

#include <stdio.h>
#include <stdlib.h>

extern void outofmemory(void);

typedef struct _SBuffer 
{
    struct _SBuffer *next;
    int        shared;
    int        bufsize;
    int        refcount;
    char       *end;
} SBuffer;

typedef struct _SBufBlock
{
    int        num;
    SBuffer*   bufs;
    struct _SBufBlock *next;
} SBufBlock;

typedef struct _SBufUser
{
    char       *start;
    SBuffer    *buf;
    struct _SBufUser *next;
} SBufUser;

typedef struct _SBufUserBlock
{
    int        num;
    SBufUser   *users;
    struct _SBufUserBlock *next;
} SBufUserBlock;

SBuffer             *largesbuf_pool = NULL, *smallsbuf_pool = NULL;
SBufUser            *user_pool = NULL;
SBufBlock           *sbuf_blocks = NULL;
SBufUserBlock       *sbufuser_blocks = NULL;
int                 sbufuser_total = 0, sbufuser_used = 0;
int                 sbufsmall_total = 0, sbufsmall_used = 0;
int                 sbuflarge_total = 0, sbuflarge_used = 0;
int                 sbufblock_used = 0, sbufuserblock_used = 0;

#define SBUF_BASE				sizeof(SBuffer)
#define SBUF_LARGE_TOTAL		(SBUF_BASE + SBUF_LARGE_BUFFER)
#define SBUF_SMALL_TOTAL        (SBUF_BASE + SBUF_SMALL_BUFFER)

int sbuf_allocblock_general(int theMemorySize, int num, SBuffer** thePool)
{
    SBufBlock*    block;
    SBuffer*      bufs;
    int           i;
    
    block = (SBufBlock*)MyMalloc(sizeof(SBufBlock));
    if (!block)
        outofmemory();
        
    block->bufs = (SBuffer*)MyMalloc(theMemorySize * num);
    if (!block->bufs)
        outofmemory();
        
    block->num = num;
    block->next = sbuf_blocks;
    sbuf_blocks = block;
    sbufblock_used++;
    
    bufs = block->bufs;
    for (i = 0; i < block->num - 1; ++i)
    {
        bufs->bufsize = theMemorySize - SBUF_BASE;
        bufs->next = (SBuffer*)(((char*)bufs) + theMemorySize);
        bufs = bufs->next;
    }
    bufs->bufsize = theMemorySize - SBUF_BASE;
    bufs->next = *thePool;
    *thePool = block->bufs;
    
    return 0;
}
    
int sbuf_allocblock_small(int theMemorySize)
{
    if (theMemorySize % SBUF_SMALL_TOTAL != 0)
        theMemorySize = (theMemorySize + SBUF_SMALL_TOTAL);
        
    sbufsmall_total += theMemorySize / SBUF_SMALL_TOTAL;
        
    return sbuf_allocblock_general(SBUF_SMALL_TOTAL, theMemorySize / SBUF_SMALL_TOTAL, &smallsbuf_pool);
}

int sbuf_allocblock_large(int theMemorySize)
{
    if (theMemorySize % SBUF_LARGE_TOTAL != 0)
        theMemorySize = (theMemorySize + SBUF_LARGE_TOTAL);
        
    sbuflarge_total += theMemorySize / SBUF_LARGE_TOTAL;
    
    return sbuf_allocblock_general(SBUF_LARGE_TOTAL, theMemorySize / SBUF_LARGE_TOTAL, &largesbuf_pool);
}

int sbuf_allocblock_users(int theCount)
{
    SBufUserBlock* block;
    SBufUser*      users;
    int            i;
    
    block = (SBufUserBlock*)MyMalloc(sizeof(SBufUserBlock));
    if (!block)
        outofmemory();
        
    block->users = (SBufUser*)MyMalloc(sizeof(SBufUser) * theCount);
    if (!block->users)
        outofmemory();
        
    block->num = theCount;
    block->next = sbufuser_blocks;
    sbufuser_blocks = block;

    sbufuserblock_used++;
    sbufuser_total += block->num;
    
    users = block->users;
    for (i = 0; i < block->num - 1; ++i)
    {
        users->next = users+1;
        users++;
    }
    users->next = user_pool;
    user_pool = block->users;

    return 0;
}
    

int sbuf_init()
{
    sbuf_allocblock_small(INITIAL_SBUFS_SMALL);
    sbuf_allocblock_large(INITIAL_SBUFS_LARGE);
    sbuf_allocblock_users(INITIAL_SBUFS_USERS);
    return 0;
}

int sbuf_free(SBuffer* buf)
{
    switch (buf->bufsize)
    {
        case SBUF_LARGE_BUFFER:
            buf->next = largesbuf_pool;
            largesbuf_pool = buf;
            sbuflarge_used--;
            break;
        
        case SBUF_SMALL_BUFFER:
            buf->next = smallsbuf_pool;
            smallsbuf_pool = buf;
            sbufsmall_used--;
            break;
        
        default:
            return -1;
    }
        
    return 0;
}

int sbuf_user_free(SBufUser* user)
{
    user->next = user_pool;
    user_pool = user;
    sbufuser_used--;
    return 0;
}
       
SBuffer* sbuf_alloc(int theSize)
{
    SBuffer* buf;
    
    if (theSize >= SBUF_SMALL_BUFFER)
    {
        buf = largesbuf_pool;
        if (!buf) {
            sbuf_allocblock_large(INITIAL_SBUFS_LARGE);
            buf = largesbuf_pool;
            if (!buf) return NULL;
        }
        largesbuf_pool = largesbuf_pool->next;
        
        sbuflarge_used++;
        
        buf->bufsize = SBUF_LARGE_BUFFER;
        buf->refcount = 0;
        buf->end = ((char*)buf) + SBUF_BASE;
        buf->next = NULL;
        buf->shared = 0;
        return buf;
    }
    else
    {
        buf = smallsbuf_pool;
        if (!buf) {
            sbuf_allocblock_small(INITIAL_SBUFS_SMALL);
            buf = smallsbuf_pool;
            if (!buf) return sbuf_alloc(SBUF_SMALL_BUFFER+1); /* attempt to substitute a large buffer instead */
        }
        smallsbuf_pool = smallsbuf_pool->next;
        
        sbufsmall_used++;
        
        buf->bufsize = SBUF_SMALL_BUFFER;
        buf->refcount = 0;
        buf->end = ((char*)buf) + SBUF_BASE;
        buf->next = NULL;
        buf->shared = 0;
        return buf;
    }
}

SBufUser* sbuf_user_alloc()
{
    SBufUser* user;
    
    user = user_pool;
    if (!user)
    {
        sbuf_allocblock_users(INITIAL_SBUFS_USERS);
        user = user_pool;
        if (!user) return NULL;
    }
    user_pool = user_pool->next;
    
    sbufuser_used++;
    
    user->next = NULL;
    user->start = NULL;
    user->buf = NULL;
    return user;
}
        

int sbuf_alloc_error()
{
    outofmemory();
    return -1;
}

/* Global functions */

int sbuf_begin_share(const char* theData, int theLength, void **thePtr)
{
    SBuffer *s;
    
    if (theLength > 510) theLength = 510;
    
    s = sbuf_alloc(theLength + 2); /* +2 for the \r\n we're tacking on to the buffer */
    if (!s || theLength + 2 > s->bufsize) return sbuf_alloc_error();
    
    memcpy(s->end, theData, theLength);
    s->end += theLength;
    *s->end++ = '\r';
    *s->end++ = '\n';
    s->refcount = 0;
    s->shared = 1;
    
    *thePtr = (void*)s;
    return 1;
}

int sbuf_end_share(void **thePtr, int theNum)
{
    SBuffer **shares = (SBuffer**)thePtr;
    int i;
      
    for (i = 0; i < theNum; ++i)
    {
        if (!shares[i]) continue;
        
        shares[i]->shared = 0;
        if (shares[i]->refcount == 0) sbuf_free(shares[i]);
    }
    
    return 0;
}
    
int sbuf_put_share(SBuf* theBuf, void* theSBuffer)
{
    SBufUser *user;
    SBuffer  *s = (SBuffer*)theSBuffer;
    
    if (!s) return -1;
    
    s->refcount++;
    user = sbuf_user_alloc();
    user->buf = s;
    user->start = (char*)(user->buf) + SBUF_BASE;
    
    if (theBuf->length == 0)
        theBuf->head = theBuf->tail = user;
    else
    {
        theBuf->tail->next = user;
        theBuf->tail = user;
    }
    theBuf->length += user->buf->end - user->start;
    return 1;
}
        
int sbuf_put(SBuf* theBuf, const char* theData, int theLength)
{
    SBufUser        **user, *u;
    int             chunk;
    
    if (theBuf->length == 0)
        user = &theBuf->head;
    else
        user = &theBuf->tail;
        
    if ((u = *user) != NULL && u->buf->refcount > 1)
    {
        u->next = sbuf_user_alloc();
        u = u->next;
        if (!u) return sbuf_alloc_error();
        *user = u; /* tail = u */
        
        u->buf = sbuf_alloc(theLength);
        u->buf->refcount = 1;
        u->start = u->buf->end;
    }
    
    theBuf->length += theLength;
    
    for (; theLength > 0; user = &(u->next))
    {
        if ((u = *user) == NULL)
        {
            u = sbuf_user_alloc();
            if (!u) return sbuf_alloc_error();
            *user = u;
            theBuf->tail = u;
            
            u->buf = sbuf_alloc(theLength);
            u->buf->refcount = 1;
            u->start = u->buf->end;
        }
        chunk = (((char*)u->buf) + SBUF_BASE + u->buf->bufsize) - u->buf->end;
        if (chunk)
        {
            if (chunk > theLength) chunk = theLength;
            memcpy(u->buf->end, theData, chunk);
           
            u->buf->end += chunk; 
            theData     += chunk;
            theLength   -= chunk;
        }
    }
    return 1;
}    

int sbuf_delete(SBuf* theBuf, int theLength)
{
    if (theLength > theBuf->length) theLength = theBuf->length;
    
    theBuf->length -= theLength;
    
    while (theLength)
    {
        int chunk = theBuf->head->buf->end - theBuf->head->start;
        if (chunk > theLength) chunk = theLength;
        
        theBuf->head->start += chunk;
        theLength           -= chunk;
        
        if (theBuf->head->start == theBuf->head->buf->end)
        {
            SBufUser *tmp = theBuf->head;
            theBuf->head = theBuf->head->next;
            
            tmp->buf->refcount--;
            if (tmp->buf->refcount == 0 && tmp->buf->shared == 0)
                sbuf_free(tmp->buf);
            sbuf_user_free(tmp);
        }
    }
    if (theBuf->head == NULL) theBuf->tail = NULL;
    
    return 1;
}

char* sbuf_map(SBuf* theBuf, int* theLength)
{
    if (theBuf->length != 0)
    {
        *theLength = theBuf->head->buf->end - theBuf->head->start;
        return theBuf->head->start;
    }
    *theLength = 0;
    return NULL;
}

#ifdef WRITEV_IOV
int sbuf_mapiov(SBuf *theBuf, struct iovec *iov)
{
    int i = 0;
    SBufUser *sbu;

    if (theBuf->length == 0)
        return 0;

    for (sbu = theBuf->head; sbu; sbu = sbu->next)
    {
        iov[i].iov_base = sbu->start;
        iov[i].iov_len = sbu->buf->end - sbu->start;
        if (++i == WRITEV_IOV)
            break;
    }

    return i;
}
#endif

int sbuf_flush(SBuf* theBuf)
{
    SBufUser *tmp;
    
    if (theBuf->length == 0) return 0;
    
    while (theBuf->head)
    {
        char *ptr = theBuf->head->start;
        while (ptr < theBuf->head->buf->end && IsEol(*ptr)) ptr++;
        
        theBuf->length -= ptr - theBuf->head->start;
        theBuf->head->start = ptr;
        if (ptr < theBuf->head->buf->end) break;
        
        tmp = theBuf->head;
        theBuf->head = tmp->next;
        
        tmp->buf->refcount--;
        if (tmp->buf->refcount == 0 && tmp->buf->shared == 0)
            sbuf_free(tmp->buf);
        sbuf_user_free(tmp);
    }
    if (theBuf->head == NULL) theBuf->tail = NULL;
    return theBuf->length;   
}

int sbuf_getmsg(SBuf* theBuf, char* theData, int theLength)
{
    SBufUser    *user;
    int         copied;
    
    if (sbuf_flush(theBuf) == 0) return 0;
    
    copied = 0;
    for (user = theBuf->head; user && theLength; user = user->next)
    {
        char *ptr, *max = user->start + theLength; 
        if (max > user->buf->end) max = user->buf->end;
        
        for (ptr = user->start; ptr < max && !IsEol(*ptr); )
            *theData++ = *ptr++;
            
        copied    += ptr - user->start;
        theLength -= ptr - user->start;
        
        if (ptr < max)
        {
            *theData = 0;
            sbuf_delete(theBuf, copied);
            sbuf_flush(theBuf);
            return copied;
        }
    }
    return 0;
}    

int sbuf_get(SBuf* theBuf, char* theData, int theLength)
{
    char   *buf;
    int    chunk, copied;
    
    if (theBuf->length == 0) return 0;
    
    copied = 0;
    while (theLength && (buf = sbuf_map(theBuf, &chunk)) != NULL)
    {
        if (chunk > theLength) chunk = theLength;
        
        memcpy(theData, buf, chunk);
        copied    += chunk;
        theData   += chunk;
        theLength -= chunk;
        sbuf_delete(theBuf, chunk);
    }
    return copied;
}

u_long
memcount_sbuf(MCsbuf *mc)
{
    mc->file = __FILE__;

    mc->smallbufpool.c = sbufsmall_total;
    mc->smallbufpool.m = sbufsmall_total * SBUF_SMALL_BUFFER;
    mc->smallbufs.c = sbufsmall_used;
    mc->smallbufs.m = sbufsmall_used * SBUF_SMALL_BUFFER;

    mc->largebufpool.c = sbuflarge_total;
    mc->largebufpool.m = sbuflarge_total * SBUF_LARGE_BUFFER;
    mc->largebufs.c = sbuflarge_used;
    mc->largebufs.m = sbuflarge_used * SBUF_LARGE_BUFFER;

    mc->userpool.c = sbufuser_total;
    mc->userpool.m = sbufuser_total * sizeof(SBufUser);
    mc->users.c = sbufuser_used;
    mc->users.m = sbufuser_used * sizeof(SBufUser);

    mc->bufblocks.c = sbufblock_used;
    mc->bufblocks.m = sbufblock_used * sizeof(SBufBlock);
    mc->userblocks.c = sbufuserblock_used;
    mc->userblocks.m = sbufuserblock_used * sizeof(SBufUserBlock);

    mc->bufheaders.c = mc->smallbufpool.c + mc->largebufpool.c;
    mc->bufheaders.m = mc->bufheaders.c * SBUF_BASE;

    mc->management.c = mc->bufheaders.c + mc->bufblocks.c;
    mc->management.m = mc->bufheaders.m + mc->bufblocks.m;
    mc->management.c += mc->userpool.c + mc->userblocks.c;
    mc->management.m += mc->userpool.m + mc->userblocks.m;

    mc->total.c = mc->smallbufpool.c + mc->largebufpool.c + mc->management.c;
    mc->total.m = mc->smallbufpool.m + mc->largebufpool.m + mc->management.m;

    return mc->total.m;
}


syntax highlighted by Code2HTML, v. 0.9.1