/* ** OSSP al -- Assembly Lists ** Copyright (c) 2002 The OSSP Project ** Copyright (c) 2002 Cable & Wireless Deutschland ** Copyright (c) 2002 Ralf S. Engelschall ** Copyright (c) 2002 Michael van Elst ** ** This file is part of OSSP al, an abstract datatype of a data buffer ** that can assemble, move and truncate data but avoids actual copying. ** ** Permission to use, copy, modify, and distribute this software for ** any purpose with or without fee is hereby granted, provided that ** the above copyright notice and this permission notice appear in all ** copies. ** ** THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR ** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ** USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ** OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ** SUCH DAMAGE. ** ** al.c: assembly lists library implementation */ #include #include #include #ifdef TEST #include #endif /****************************************************************************/ #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); \ else \ HEAD(q,l) = NEXT(n,l); \ if (NEXT(n,l)) \ PREV(NEXT(n,l),l) = PREV(n,l); \ else \ TAIL(q,l) = PREV(n,l); \ NEXT(n,l) = NULL; \ PREV(n,l) = NULL; \ } while (0) #define INSERT(q,l,i,n) \ do { \ if (PREV(i,l)) { \ NEXT(PREV(i,l),l) = (n); \ } else { \ HEAD(q,l) = (n); \ } \ PREV(n,l) = PREV(i,l); \ 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 */ struct al_buffer_st; typedef struct al_buffer_st al_buffer_t; 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 { 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 * we must not grow a chunk in a shared buffer since * we do not track other chunks sharing the buffer */ #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 * we must not grow a chunk in a shared buffer since * we do not track other chunks sharing the buffer */ #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; } #ifdef TEST static void dump(al_t *al) { al_chunk_t *cur; size_t total; printf("AL: %p\n", al); total = 0; FOREACH(al,chunks,cur) { printf(" C: %p (%d @ %p + %d < %d (use=%d))\n", cur, cur->buf->size,cur->buf->mem, cur->begin,cur->end, cur->buf->usecount); total += AL_CHUNK_LEN(cur); } printf("size = %d == %d\n",al->bytes,total); 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; #ifdef TEST al->m.new_buffersize = 42; #else al->m.new_buffersize = 4096; #endif 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) { REMOVE(al,chunks,cur); dispose_chunk(al,cur); } /* free object itself */ /* XXX - which free() ? */ free(al); return AL_OK; } /* * copy bytes into buffer, FIFO * * stops copy operation when a new buffer cannot be created * but leaves data structure consistent */ 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; /* argument sanity check(s) */ if (al == NULL || src == NULL || n < 0) return AL_RC(AL_ERR_ARG); cur = TAIL(al,chunks); res = AL_CHUNK_RESERVE(cur); while (n > 0) { if (res == 0) { rc = new_buffer(al, &buf); if (rc != AL_OK) return AL_RC(rc); rc = new_chunk(al,buf,&cur); if (rc != AL_OK) return AL_RC(rc); 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 * * stops copy operation when a new buffer cannot be created * but leaves data structure consistent */ 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; /* argument sanity check(s) */ if (al == NULL || src == NULL || n < 0) return AL_RC(AL_ERR_ARG); 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 AL_RC(rc); rc = new_chunk(al,buf,&cur); if (rc != AL_OK) return AL_RC(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; /* argument sanity check(s) */ if (al == NULL || p == NULL || n < 0) return AL_RC(AL_ERR_ARG); rc = make_buffer(al, p, n, &buf); if (rc != AL_OK) return AL_RC(rc); rc = new_chunk(al,buf, &cur); if (rc != AL_OK) return AL_RC(rc); ADDTAIL(al, chunks, cur); /* validate data in buffer */ AL_RESIZE(al, cur, n); return AL_OK; } /* * this is the central function to manipulate assembly lists * * cut arbitrary spans from list into a destination buffer (or drop it) * insert (destructive move) content from another list into the cut * * this is analog to perls splice function, except that * -> the output is not stored in the target buffer but is appended * -> the replacement data is moved and not copied. * */ 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, *ins, *splitbuf; size_t pos, skip, len, step; int doinsert; /* argument sanity check(s) */ if (al == NULL || n < 0) return AL_RC(AL_ERR_ARG); /* optimization: treat an empty list as no insertion at all */ doinsert = (nal != NULL) && !ISEMPTY(nal,chunks); /* * 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 AL_RC(rc); /* * remember insertion point * * caveat: if the first chunk is removed from input the insertion * point shifts to the next chunk (or EOD -> NULL pointer) */ ins = cur; /* * the inseration point is either behind the list or * within the current chunk * * if it is behind the list: * -> insert operation switches to append mode * * if the insertion point is at the beginning of the current chunk: * -> insertion point moves later if the chunk is removed * * if the inseration point is in the middle of the current chunk: * -> chunk is split into two so that the insertion * point is at the beginning of the second part * * insertion point cannot be at EOD of the chunk * * splitting at this point preserves all data in case the * allocation of the split buffer fails */ if (doinsert) { if (ins != NULL && skip > 0) { rc = split_chunk(al, ins, skip, &splitbuf); if (rc != AL_OK) return AL_RC(rc); INSERT(al,chunks,ins,splitbuf); } } /* * 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); /* copy data to target */ if (tal != NULL) { /* * XXX - this operation can fail */ size_t before = tal->bytes; rc = al_append_bytes(tal, AL_CHUNK_PTR(cur, skip), step); if (rc != AL_OK) { /* correct step size to actual size */ step = tal->bytes - before; } } else { rc = AL_OK; } /* * 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); } /* * bail out from failed al_append_bytes operation above * */ if (rc != AL_OK) return AL_RC(rc); } 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); /* * when the insertion chunk is removed, we have to adjust * the insertion point * * if the insertion chunk was the last chunk we get a NULL * next pointer and the insertion method switches to append * mode */ if (cur == ins) ins = next; al->bytes -= step; if (tal == NULL) { dispose_chunk(al, cur); } else { ADDTAIL(tal, chunks, cur); tal->bytes += step; } } n -= step; pos += step; cur = next; skip = 0; } /* * now splice in replacement chunks */ if (doinsert) { if (ins != NULL) { /* * complex case where list is inserted * * the original list has already been modified so * that we can simply insert between two chunks */ INSERTLIST(al,chunks,ins,nal); } else { /* * simple case where list end is 'replaced' */ APPENDLIST(al,chunks,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 **txp) { al_tx_t *tx; tx = (al_tx_t*)(al->m.malloc)(sizeof(al_tx_t)); if (tx == NULL) return AL_RC(AL_ERR_MEM); *txp = tx; return AL_OK; } /* * free traversal context using proper policy function */ al_rc_t al_txfree(al_t *al, al_tx_t *tx) { (al->m.free)(tx); 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 *tx) { al_rc_t rc; tx->cur = NULL; rc = al_seek(al, off, &tx->cur, &tx->skip); if (rc != AL_OK) return AL_RC(rc); tx->dir = dir; tx->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 *tx, al_chunk_t **alcp) { size_t step; if (tx->togo <= 0) /* XXX - togo can be negative from bad input */ return AL_ERR_EOF; if (tx->cur == NULL) /* premature EOF */ return AL_ERR_EOF; step = AL_CHUNK_LEN(tx->cur); if (step > tx->togo) step = tx->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 */ tx->view = *(tx->cur); tx->view.begin += tx->skip; tx->view.end = tx->view.begin + step; switch (tx->dir) { case AL_FORWARD: tx->cur = NEXT(tx->cur,chunks); tx->togo -= step; break; case AL_BACKWARD: tx->cur = PREV(tx->cur,chunks); tx->togo -= step; break; } *alcp = &tx->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 AL_RC(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 AL_RC(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; *lenp = 0; /* keep caller on safe side */ rc = al_traverse(al, off, n, AL_FORWARD, &tx); if (rc != AL_OK) return AL_RC(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 AL_RC(rc); return AL_OK; } /* * full list traversal with data copy to target assembly list * * traversal context is kept on stack (XXX ?) */ al_rc_t al_copy(al_t *al, size_t off, size_t n, al_t *tal) { al_rc_t rc; al_tx_t tx; /* XXX - private tx structure on stack */ al_chunk_t *view; size_t step; rc = al_traverse(al, off, n, AL_FORWARD, &tx); if (rc != AL_OK) return AL_RC(rc); while ((rc = al_traverse_next(al, &tx, &view)) == AL_OK) { step = AL_CHUNK_LEN(view); al_append_bytes(tal, AL_CHUNK_PTR(view,0), step); } if (rc != AL_ERR_EOF) return AL_RC(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; }