/*
** OSSP al -- Assembly Line
** 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 line library implementation
*/
#include
#include
#include
#ifdef TEST
#include
#endif
#include "list.h"
#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); /* malloc(3) style function (for al_chunk_t) */
void (*free)(void *); /* free(3) style function (for al_chunk_t) */
void *(*balloc)(size_t); /* malloc(3) style function (for al_buffer_t) */
void (*bfree)(void *); /* free(3) style function (for al_buffer_t) */
size_t new_buffersize; /* default size for memory underlying al_buffer_t */
int max_freechunks; /* maximum number of cached al_chunk_t objects */
} al_memops_t;
struct al_st {
LIST(al_chunk_t) chunks; /* list header for al_chunk_t objects */
size_t bytes; /* total cached number of bytes in chunk */
al_memops_t m; /* assembly line memory operations (see above) */
};
struct al_chunk_st {
NODE(al_chunk_t) chunks; /* list node for al_chunk_t object chaining */
al_buffer_t *buf; /* (non-exlusively) referenced buffer object */
size_t begin; /* offset into buf->mem where data starts */
size_t end; /* offset into buf->mem where data ends */
};
struct al_buffer_st {
char *mem; /* reference to underlying chunk of data */
size_t size; /* size of underlying chunk of data */
int usecount; /* reference count (from al_chunk_t) */
void (*freemem)(char *p, size_t n, void *u); /* callback function to reclaim memory when it is no longer referenced */
void *userdata; /* arbitrary pointer to pass context data to the callback function */
};
struct al_tx_st {
al_td_t dir; /* traversal direction */
al_chunk_t *cur; /* current chunk during traveral steps */
size_t skip; /* number of bytes to skip for traversal */
size_t togo; /* number of bytes left for traversal */
al_chunk_t view; /* synthetic chunk for returning during traversal steps */
};
/* 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) != NULL \
? ( (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) != NULL \
? ( (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)
/*
* callback to release buffer memory allocated by the library
*/
static
void freemem(char *p, size_t n, void *u)
{
printf("*** FREEMEM(LIBRARY) buffer %p size %d\n",p,n);
al_t *al = (al_t *)u;
(al->m.bfree)(p);
}
/*
* 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 = freemem;
buf->userdata = NULL;
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)
(buf->freemem)(buf->mem, buf->size, buf->userdata);
(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, void (*freemem)(char *, size_t, void *), void *u, 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 = freemem;
buf->userdata = u;
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 {
/* thl: FIXME i would suggest using REMTAIL() here because dispose_chunk
* puts the latest disposed chunks at the end of the list and
* those latest data should have a closer approximation to the
* CPU in terms of internal cache, external cache, RAM and VM
*/
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) /* off is negative */
off += al->bytes;
if ((al->bytes / 2) >= off) { /* FIXME poor man's heuristic */
/* forward search */
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 {
/* reverse search */
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 line
* 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 line 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, void (*freemem)(char *, size_t, void *), void *u)
{
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, freemem, u, &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 line
*
* 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;
}
/*
* assembly line 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 assembly line 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;
}
/*
* assembly line traversal step
*
* return EOF if at end of assembly line
* 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;
}
/*
* assembly line traversal end
*
* to free resources allocated/locked during traversal
* currently a NOOP
*/
al_rc_t al_traverse_end(al_t *al, al_tx_t *tx, int final)
{
return AL_OK;
}
/*
* full assembly line 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;
}
al_traverse_end(al, &tx, 1);
if (rc != AL_ERR_EOF)
return AL_RC(rc);
return AL_OK;
}
/*
* full assembly line 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;
al_traverse_end(al, &tx, 1);
if (rc != AL_ERR_EOF)
return AL_RC(rc);
return AL_OK;
}
/*
* full assembly line traversal with data copy to target assembly line
*
* 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);
}
al_traverse_end(al, &tx, 1);
if (rc != AL_ERR_EOF)
return AL_RC(rc);
return AL_OK;
}
/*
* relay macros to caller
*
* al_bytes - total number of bytes in assembly line
* 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;
}