OSSP CVS Repository

ossp - ossp-pkg/sio/al.c 1.4
Not logged in
[Honeypot]  [Browse]  [Directory]  [Home]  [Login
[Reports]  [Search]  [Ticket]  [Timeline
  [Raw

ossp-pkg/sio/al.c 1.4
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

/****************************************************************************/

#define LIST(elem) \
    struct { elem *head, *tail; }
#define NODE(elem) \
    struct { elem *next, *prev; }

#define HEAD(q,l)       ((q)->l.head)
#define TAIL(q,l)       ((q)->l.tail)
#define NEXT(n,l)       ((n)->l.next)
#define PREV(n,l)       ((n)->l.prev)

#define ISEMPTY(q,l)    (HEAD(q,l) == NULL)

#define LISTINIT(q,l) \
do { \
    HEAD(q,l) = NULL; \
    TAIL(q,l) = NULL;  \
} while(0)

#define NODEINIT(n,l) \
do { \
    NEXT(n,l) = NULL; \
    PREV(n,l) = NULL; \
} while(0)

#define ADDTAIL(q,l,n) \
do { \
    if (TAIL(q,l)) { \
        NEXT(TAIL(q,l),l) = (n); \
        PREV(n,l) = TAIL(q,l); \
    } else { \
        PREV(n,l) = NULL; \
        HEAD(q,l) = (n); \
    } \
    TAIL(q,l) = (n); \
    NEXT(n,l) = NULL; \
} while (0)

#define ADDHEAD(q,l,n) \
do { \
    if (HEAD(q,l)) { \
        PREV(HEAD(q,l),l) = (n); \
        NEXT(n,l) = HEAD(q,l); \
    } else { \
        NEXT(n,l) = NULL; \
        TAIL(q,l) = (n); \
    } \
    HEAD(q,l) = (n); \
    PREV(n,l) = NULL; \
} while (0)

#define REMHEAD(q,l,n) \
do { \
    (n) = HEAD(q,l); \
    if (n) { \
        HEAD(q,l) = NEXT(n,l); \
        if (HEAD(q,l)) \
            PREV(HEAD(q,l),l) = NULL; \
        else \
            TAIL(q,l) = NULL; \
    } \
} while(0)

#define REMTAIL(q,l,n) \
do { \
    (n) = TAIL(q,l); \
    if (n) { \
        TAIL(q,l) = PREV(n,l); \
        if (TAIL(q,l)) \
            NEXT(TAIL(q,l),l) = NULL; \
        else \
            HEAD(q,l) = NULL; \
    } \
} while(0)

#define REMOVE(q,l,n) \
do { \
    if (PREV(n,l)) \
        NEXT(PREV(n,l),l) = NEXT(n,l); \
    if (NEXT(n,l)) \
        PREV(NEXT(n,l),l) = PREV(n,l); \
} while (0)

#define INSERT(q,l,i,n) \
do { \
    if (PREV(i,l)) { \
        PREV(n,l) = PREV(i,l); \
        NEXT(PREV(i,l),l) = (n); \
    } \
    PREV(i,l) = (n); \
    NEXT(n,l) = (i); \
} while (0)

#define APPENDLIST(q1,l,q2) \
do { \
    if (TAIL(q1,l)) { \
        NEXT(TAIL(q1,l),l) = HEAD(q2,l); \
        PREV(HEAD(q2,l),l) = TAIL(q1,l); \
    } else { \
        HEAD(q1,l) = HEAD(q2,l); \
    } \
    TAIL(q1,l) = TAIL(q2,l); \
    LISTINIT(q2,l); \
} while(0)

#define INSERTLIST(q1,l,i,q2) \
do { \
    if (PREV(i,l)) { \
        NEXT(PREV(i,l),l) = HEAD(q2,l); \
    } else { \
        HEAD(q1,l) = HEAD(q2,l); \
    } \
    PREV(HEAD(q2,l),l) = PREV(i,l); \
    NEXT(TAIL(q2,l),l) = (i); \
    PREV(i,l) = TAIL(q2,l); \
    LISTINIT(q2,l); \
} while(0)

#define FOREACH(q,l,n)    for (n = HEAD(q,l); n; n = NEXT(n,l))
#define FOREACHR(q,l,n)   for (n = TAIL(q,l); n; n = PREV(n,l))
#define FOREACHD(q,l,n,r) for (n = TAIL(q,l); n && (r = PREV(n,l), 1); n = r)

/****************************************************************************/

#include "al.h"

/* unique library identifier */
const char al_id[] = "OSSP al";

/* support for OSSP ex based exception throwing */
#ifdef WITH_EX
#include "ex.h"
#define AL_RC(rv) \
    (  (rv) != AL_OK && (ex_catching && !ex_shielding) \
     ? (ex_throw(al_id, NULL, (rv)), (rv)) : (rv) )
#else
#define AL_RC(rv) (rv)
#endif /* WITH_EX */

typedef struct {
    void   *(*malloc)(size_t);
    void   (*free)(void *);
    void   *(*balloc)(size_t);
    void   (*bfree)(void *);
    size_t new_buffersize;
    int    max_freechunks;
} al_memops_t;

struct al_st {
    LIST(al_chunk_t) chunks;
    size_t           bytes;
    al_memops_t      m;
};

struct al_chunk_st {
    NODE(al_chunk_t) chunks;
    al_buffer_t      *buf;
    size_t           begin, end;
};

struct al_buffer_st {
    unsigned char  *mem;
    size_t         size;
    int            usecount;
    int            freemem;
};

struct al_tx_st {
    al_td_t       dir;
    al_chunk_t    *cur;
    size_t        skip;
    size_t        togo;
    al_chunk_t    view;
};

/* number of bytes described by a chunk */
#define AL_CHUNK_LEN(alc) \
    ((alc)->end - (alc)->begin)

/* memory pointer into a chunk, offset must be less than length */
#define AL_CHUNK_PTR(alc, off) \
    ((alc)->buf->mem + (alc)->begin + (off))

/* number of bytes of a span that are stored in a chunk */
#define AL_CHUNK_SPAN(alc, off, n) \
    ((n) > AL_CHUNK_LEN(alc) - (off) ? \
    AL_CHUNK_LEN(alc) - (off) : (n))

/* number of bytes a chunk can be grown to the end */
#define AL_CHUNK_RESERVE(alc) \
    ((alc) \
    ? (alc)->buf->usecount > 1 ? 0 : (alc)->buf->size - (alc)->end \
    : 0)

/* grow chunk to the end */
#define AL_RESIZE(al, alc, n) \
    do { (alc)->end += n; (al)->bytes += (n); } while (0)

/* number of bytes a chunk can be grown to the beginning */
#define AL_CHUNK_PRESERVE(alc) \
    ((alc) \
    ? (alc)->buf->usecount > 1 ? 0 : (alc)->begin \
    : 0)

/* grow chunk to the beginning */
#define AL_PRESIZE(al, alc, n) \
    do { (alc)->begin -= n; (al)->bytes += (n); } while (0)

/*
 * allocate backing store and its control structure from heap
 *
 * can be freed when usecount drops to zero
 */
static
al_rc_t new_buffer(al_t *al, al_buffer_t **bufp)
{
    size_t n = al->m.new_buffersize;
    al_buffer_t *buf;

    if ((buf = (al_buffer_t *)(al->m.malloc)(sizeof(al_buffer_t))) == NULL)
        return AL_ERR_MEM;

    if ((buf->mem = (al->m.balloc)(n)) == NULL) {
        (al->m.free)(buf);
        return AL_ERR_MEM;
    }

    buf->freemem  = 1;
    buf->size     = n;
    buf->usecount = 0;

    *bufp = buf;
    return AL_OK;
}

static
al_rc_t dispose_buffer(al_t *al, al_buffer_t *buf)
{
    /* must not happen */
    if (buf->usecount != 0)
        return AL_ERR_INT;

    if (buf->freemem)
        (al->m.bfree)(buf->mem);

    (al->m.free)(buf);
    return AL_OK;
}

/*
 * allocate only control structure for backing store from heap
 * and attach to existing memory
 * only the control structure will be freed later
 */
static
al_rc_t make_buffer(al_t *al, char *p, size_t n, al_buffer_t **bufp)
{
    al_buffer_t *buf;

    if ((buf = (al_buffer_t *)(al->m.malloc)(sizeof(al_buffer_t))) == NULL)
        return AL_ERR_MEM;

    buf->mem      = p;

    buf->freemem  = 0;
    buf->size     = n;
    buf->usecount = 0;

    *bufp = buf;
    return AL_OK;
}

/*
 * allocate chunks from heap and attach to backing store
 * keep some freed chunks in a freelist to avoid expensive malloc()
 * and excessive fragmentation
 * maintain usage count of backing store
 * free backing store when no longer in use, this also includes
 * the case where new_chunk fails and the buffer has no other
 * users
 */
static
struct {
    LIST(al_chunk_t) chunks;
} alc_freelist;
static
int alc_freecount = 0;

static
al_rc_t new_chunk(al_t *al, al_buffer_t *buf, al_chunk_t **alcp)
{
    al_chunk_t *alc;

    if (ISEMPTY(&alc_freelist, chunks)) {
        if ((alc = (al_chunk_t *)(al->m.malloc)(sizeof(al_chunk_t))) == NULL) {
            if (buf->usecount == 0)
                dispose_buffer(al,buf);
            return AL_ERR_MEM;
        }
    } else {
        REMHEAD(&alc_freelist, chunks, alc);
        --alc_freecount;
    }

    NODEINIT(alc, chunks);
    alc->buf   = buf;
    alc->begin = 0;
    alc->end   = 0;

    ++buf->usecount;

    *alcp = alc;
    return AL_OK;
}

static
al_rc_t split_chunk(al_t *al, al_chunk_t *orig, size_t off, al_chunk_t **alcp)
{
    al_rc_t rc;
    al_chunk_t *alc;
    size_t len = AL_CHUNK_LEN(orig);

    if (off < 0 || off > len)
        return AL_ERR_ARG;

    rc = new_chunk(al, orig->buf, &alc);
    if (rc != AL_OK) return rc;

    alc->begin  = orig->begin;
    alc->end    = orig->begin+off;
    orig->begin = alc->end;

    *alcp = alc;
    return AL_OK;
}

static
void dispose_chunk(al_t *al, al_chunk_t *alc)
{
        --alc->buf->usecount;
        if (alc->buf->usecount == 0)
            dispose_buffer(al,alc->buf);
        alc->buf = NULL;

        /* stop freelist from growing infinitely */
        if (alc_freecount >= al->m.max_freechunks) {
            (al->m.free)(alc);
        } else {
            ADDTAIL(&alc_freelist, chunks, alc);
            ++alc_freecount;
        }
}

/*
 *  find chunk that represents a particular offset
 *  if off is negative, treat it as relative offset from the end
 *  a reference to the chunk is stored in *alcp
 *  the relative offset into the chunk is stored in *skipp
 *  return AL_OK and *alcp == NULL if positioned exactly to end of list
 *  return AL_ERR_EOF when no such chunk can be found
 */
static
al_rc_t al_seek(al_t *al, size_t off, al_chunk_t **alcp, size_t *skipp)
{
    al_chunk_t *cur;
    size_t pos, end;
    size_t chunksize;

    if (off >= 0) {
        pos = 0;
        FOREACH(al,chunks,cur) {
            chunksize = AL_CHUNK_LEN(cur);
            end = pos+chunksize;
            if (pos <= off && off < end) {
                *alcp  = cur;
                *skipp = off - pos;
                return AL_OK;
            }
            if (end > off)
                break;
            pos = end;
        }
        /* seek to EOF position is ok */
        if (pos == off) {
            *alcp  = NULL;
            *skipp = 0;
            return AL_OK;
        }
    } else {
        off += al->bytes;
        pos  = al->bytes;
        FOREACHR(al,chunks,cur) {
            chunksize = AL_CHUNK_LEN(cur);
            end  = pos;
            pos -= chunksize;
            if (pos <= off && off < end) {
                *alcp  = cur;
                *skipp = off - pos;
                return AL_OK;
            }
            if (pos < off)
                break;
        }
    }

    return AL_ERR_EOF;
}

#if 0
#include <stdio.h>
static
void dump(al_t *al)
{
    al_chunk_t *cur;

    printf("AL: %x\n", al);
    FOREACH(al,chunks,cur) {
        printf(" C: %x (%d @ %x + %d < %d)\n",
            cur,
            cur->buf->size,cur->buf->mem,
            cur->begin,cur->end);
    }
    printf("--\n\n");
}
#endif

/****************************************************************************/

/*
 * allocate an empty assembly list
 * dispose all chunks and all allocated backing store
 */
al_rc_t al_create(al_t **alp)
{
    al_t *al;

    /* argument sanity check(s) */
    if (alp == NULL)
            return AL_RC(AL_ERR_ARG);

    /* allocate and initialize new assembly list object */
    /* XXX - what malloc ? */
    if ((al = (al_t *)malloc(sizeof(al_t))) == NULL)
            return AL_RC(AL_ERR_MEM);

    LISTINIT(al,chunks);
    al->bytes = 0;

    /* memory policy */
    al->m.malloc = malloc;    /* structure allocation */
    al->m.free   = free;
    al->m.balloc = malloc;    /* buffer allocation */
    al->m.bfree  = free;
    al->m.new_buffersize = 4096;
    al->m.max_freechunks = 500;

    /* pass object to caller */
    *alp = al;

    return AL_OK;
}

al_rc_t al_destroy(al_t *al)
{
    al_chunk_t *cur, *pred;

    /* argument sanity check(s) */
    if (al == NULL)
            return AL_RC(AL_ERR_ARG);

    /* free chunks and backing store */
    FOREACHD(al,chunks,cur,pred) {
        dispose_chunk(al,cur);
    }

    /* free object itself */
    /* XXX - which free() ? */
    free(al);

    return AL_OK;
}

/*
 * copy bytes into buffer, FIFO
 */
al_rc_t al_append_bytes(al_t *al, const char *src, size_t n)
{
    al_rc_t rc;
    al_chunk_t *cur;
    al_buffer_t *buf;
    size_t res, step;
    char *dst;

    cur = TAIL(al,chunks);
    res = AL_CHUNK_RESERVE(cur);

    while (n > 0) {
        if (res == 0) {
            rc = new_buffer(al, &buf);
            /* XXX handle error */
            rc = new_chunk(al,buf,&cur);
            /* XXX handle error */
            res = AL_CHUNK_RESERVE(cur);
            ADDTAIL(al, chunks, cur);
        }
        step = n;
        if (step > res)
            step = res;

        dst = AL_CHUNK_PTR(cur, AL_CHUNK_LEN(cur));
        memcpy(dst, src, step);

        src += step;
        AL_RESIZE(al, cur, step);
        n   -= step;
        res = AL_CHUNK_RESERVE(cur);
    }

    return AL_OK;
}

/*
 * copy bytes into buffer, LIFO
 */
al_rc_t al_prepend_bytes(al_t *al, const char *src, size_t n)
{
    al_rc_t rc;
    al_chunk_t *cur;
    al_buffer_t *buf;
    size_t res, step;
    char *dst;

    cur = HEAD(al,chunks);
    res = AL_CHUNK_PRESERVE(cur);

    src += n;

    while (n > 0) {
        if (res == 0) {
            rc = new_buffer(al, &buf);
            if (rc != AL_OK) return rc;
            rc = new_chunk(al,buf,&cur);
            if (rc != AL_OK) return rc;
            res = AL_CHUNK_PRESERVE(cur);
            ADDHEAD(al, chunks, cur);
        }
        step = n;
        if (step > res)
            step = res;

        src -= step;
        AL_RESIZE(al, cur, step);
        n   -= step;
        res = AL_CHUNK_RESERVE(cur);

        dst = AL_CHUNK_PTR(cur, 0);
        memcpy(dst, src, step);
    }

    return AL_OK;
}

/*
 * append a caller supplied buffer
 *
 * buffer must exist until list is destroyed
 * XXX - buffer can have multiple refs caused by splice operations
 * XXX - some list operations modify the buffer
 *
 */
al_rc_t al_attach_buffer(al_t *al, char *p, size_t n)
{
    al_rc_t rc;
    al_buffer_t *buf;
    al_chunk_t  *cur;

    rc = make_buffer(al, p, n, &buf);
    if (rc != AL_OK) return rc;
    rc = new_chunk(al,buf, &cur);
    if (rc != AL_OK) return rc;
    ADDTAIL(al, chunks, cur);

    /* validate data in buffer */
    AL_RESIZE(al, cur, n);

    return AL_OK;
}

/*
 *
 *
 *
 */
al_rc_t al_splice(al_t *al, size_t off, size_t n, al_t *nal, al_t *tal)
{
    al_rc_t rc;
    al_chunk_t *cur, *next;
    size_t pos, skip, len, step;

    /*
     * seek to beginning, return EOF when seek position does not exist
     * EOD must be a valid seek position so that we can append data
     */
    rc = al_seek(al, off, &cur, &skip);
    if (rc != AL_OK) return rc;

    /* as long as there is data to move */
    pos = off;
    while (n > 0 && cur != NULL) {
        next = NEXT(cur, chunks);
        len  = AL_CHUNK_LEN(cur);

        /*
         * check if partial chunk was selected
         * if skip > 0 we have to preserve bytes at the beginning
         * if skip == 0 && n < len-skip we have to preserve bytes at the end
         */
        if (skip > 0 || n < len - skip) {

            /* compute number of bytes to process */
            step = AL_CHUNK_SPAN(cur, skip, n);

            if (tal != NULL)
                al_append_bytes(tal, AL_CHUNK_PTR(cur, skip), step);

            /*
             * cut span from src chunk
             * if skip == 0, simply shrink the chunk from the beginning
             * if skip > 0, compute number of bytes to preserve,
             *              align buffer and shrink chunk from the end
             */
            if (skip == 0) {
                AL_PRESIZE(al, cur, -step);
            } else {
                /* align trailing bytes */
                if (len > (skip+step)) {
                    memmove(
                        AL_CHUNK_PTR(cur, skip),
                        AL_CHUNK_PTR(cur, skip+step),
                        len - (skip+step)
                    );
                }
                AL_RESIZE(al, cur, -step);
            }

        } else {

            /*
             * the whole chunk has to be moved, simply move it
             * to the target chain
             * manual accounting for total size
             */
            step = len;
            REMOVE(al, chunks, cur);
            al->bytes -= step;
            if (tal != NULL) {
                ADDTAIL(tal, chunks, cur);
                tal->bytes += step;
            } else {
                dispose_chunk(al, cur);
            }

        }
        n   -= step;
        pos += step;
        cur  = next;
        skip = 0;
    }

    /*
     * now splice in replacement chunks
     */
    if (nal && !ISEMPTY(nal,chunks)) {
        al_chunk_t *ins;

        /* rewind */
        rc = al_seek(al, off, &ins, &skip);
        if (rc != AL_OK) return rc;

        if (ins == NULL) {

            /*
             * simple case where list end is 'replaced'
             */
            APPENDLIST(al,chunks,nal);

        } else {

            /*
             * if beginning and end were in same chunk we have to split this
             * chunk in two for a copyless insert operation
             */
            if (skip > 0) {
                al_chunk_t *twin;
                rc = split_chunk(al, ins, skip, &twin);
                if (rc != AL_OK) return rc;

                INSERT(al,chunks,ins,twin);
            }
            INSERTLIST(al,chunks,ins,nal);

        }

        al->bytes += nal->bytes;
        nal->bytes = 0;
    }

    return AL_OK;
}

/*
 * list traversal requires a context. It needs to be
 * malloced to keep its inner structure private
 */
al_rc_t al_txalloc(al_t *al, al_tx_t **txpp)
{
    al_tx_t *txp;

    txp = (al_tx_t*)(al->m.malloc)(sizeof(al_tx_t));
    if (txp == NULL)
        return AL_ERR_MEM;

    *txpp = txp;
    return AL_OK;
}

/*
 * free traversal context using proper policy function
 */
al_rc_t al_txfree(al_t *al, al_tx_t *txp)
{
    (al->m.free)(txp);
    return AL_OK;
}

/*
 * initiate list traversal
 *
 * - do initial seek, fail if position does not exist
 * - save traversal parameters
 */
al_rc_t al_traverse(al_t *al, size_t off, size_t n, al_td_t dir, al_tx_t *txp)
{
    al_rc_t rc;

    txp->cur = NULL;

    rc = al_seek(al, off, &txp->cur, &txp->skip);
    if (rc != AL_OK) return rc;

    txp->dir  = dir;
    txp->togo = n;

    return AL_OK;
}

/*
 * list traversal step
 *
 * return EOF if at end of list
 * clip view chunk to traversal bounds
 * advance chunk cursor according to traversal direction
 */
al_rc_t al_traverse_next(al_t *al, al_tx_t *txp, al_chunk_t **alcp)
{
    size_t step;

    if (txp->cur == NULL)
        return AL_ERR_EOF;

    step = AL_CHUNK_LEN(txp->cur);
    if (step > txp->togo)
        step = txp->togo;

    /*
     * synthetic chunk which is NOT maintained in usecount
     * MUST NOT BE USED for modifications to chunk list
     * MUST NOT BE USED for modifications to chunk size
     * ALLOWED is read access to chunk content
     * ALLOWED is modification in place of chunk content
     */
    txp->view        = *(txp->cur);
    txp->view.begin += txp->skip;
    txp->view.end    = txp->view.begin + step;

    switch (txp->dir) {
    case AL_FORWARD:
        txp->cur   = NEXT(txp->cur,chunks);
        txp->togo -= step;
        break;
    case AL_BACKWARD:
        txp->cur   = PREV(txp->cur,chunks);
        txp->togo -= step;
        break;
    }

    *alcp = &txp->view;
    return AL_OK;
}

/*
 * full list traversal with callback
 *
 * traversal is aborted when callback return != AL_OK
 * reaching EOF (and also aborting with AL_ERR_EOF) is not an error
 *
 * traversal context is kept on stack (XXX ?)
 */
al_rc_t al_traverse_cb(al_t *al, size_t off, size_t n, al_td_t dir,
                       al_rc_t (*cb)(al_chunk_t *, void *), void *u)
{
    al_rc_t rc;
    al_tx_t tx;                /* XXX - private tx structure on stack */
    al_chunk_t *view;

    rc = al_traverse(al, off, n, dir, &tx);
    if (rc != AL_OK) return rc;

    while ((rc = al_traverse_next(al, &tx, &view)) == AL_OK) {
        rc = cb(view, u);
        if (rc != AL_OK)
            break;
    }

    if (rc != AL_ERR_EOF)
        return rc;

    return AL_OK;
}

/*
 * full list traversal with data copy to linear buffer
 *
 * returns actual number of bytes copied
 *
 * traversal context is kept on stack (XXX ?)
 */
al_rc_t al_flatten(al_t *al, size_t off, size_t n, char *dst, size_t *lenp)
{
    al_rc_t rc;
    al_tx_t tx;                /* XXX - private tx structure on stack */
    al_chunk_t *view;
    size_t step, total;

    rc = al_traverse(al, off, n, AL_FORWARD, &tx);
    if (rc != AL_OK) return rc;

    total = 0;
    while ((rc = al_traverse_next(al, &tx, &view)) == AL_OK) {
        step = AL_CHUNK_LEN(view);
        memmove(dst, AL_CHUNK_PTR(view,0), step);
        dst   += step;
        total += step;
    }
    *lenp = total;

    if (rc != AL_ERR_EOF)
        return rc;

    return AL_OK;
}

/*
 * relay macros to caller
 *
 * al_bytes      - total number of bytes in list
 * al_chunk_len  - number of bytes in chunk
 * al_chunk_span - clip interval (off,off+n( against chunk length
 * al_chunk_ptr  - return memory pointer to byte within chunk
 *
 * off must be a valid offset within (0,len(
 */
size_t al_bytes(const al_t *al)
{
    return al->bytes;
}
size_t al_chunk_len(al_chunk_t *alc)
{
    return AL_CHUNK_LEN(alc);
}
size_t al_chunk_span(al_chunk_t *alc, size_t off, size_t n)
{
    return AL_CHUNK_SPAN(alc, off, n);
}
char *al_chunk_ptr(al_chunk_t *alc, size_t off)
{
    return AL_CHUNK_PTR(alc, off);
}

/*
 * convert error code into human readable form
 */
const char *al_error(al_rc_t rc)
{
    const char *mess;

    switch (rc) {
    case AL_OK:       mess = "Everything Ok"; break;
    case AL_ERR_ARG:  mess = "Invalid Argument"; break;
    case AL_ERR_MEM:  mess = "Not Enough Memory"; break;
    case AL_ERR_EOF:  mess = "End Of Data"; break;
    case AL_ERR_INT:  mess = "Internal Error"; break;
    default:          mess = "Invalid Result Code"; break;
    }

    return mess;
}


CVSTrac 2.0.1