OSSP CVS Repository

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

ossp-pkg/val/val.c 1.3
/*
**  val - OSSP Value Library
**  Copyright (c) 2002 Ralf S. Engelschall <rse@engelschall.com>
**  Copyright (c) 2002 The OSSP Project <http://www.ossp.org/>
**  Copyright (c) 2002 Cable & Wireless Deutschland <http://www.cw.com/de/>
**
**  This file is part of OSSP val, a Value library which
**  can be found at http://www.ossp.org/pkg/val/.
**
**  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.
**
**  val.c: library implementation
*/

/* include optional Autoconf header */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

/* include system API headers */
#include <stdio.h>       /* for "size_t" */
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

/* include own API header */
#include "val.h"

/* boolean values */
#ifndef FALSE
#define FALSE (0)
#endif
#ifndef TRUE
#define TRUE  (!FALSE)
#endif

/*
**  ____                           ____
**  ____ LINEAR HASING SUB-LIBRARY ____
**
**  This part implements a Dynamic Hash Table, based on WITOLD LITWIN
**  and PER-AKE LARSON's ``Linear Hashing'' algorithm (1980/1988),
**  implemented on top of a two-level virtual array with separate
**  collision chains as the backend data structure. Some ideas were
**  taken over from MIKAEL PETTERSON's Linear Hashing enhancements
**  (1993). The code is derived from OSSP act. See there for details.
*/

struct lh_st;
typedef struct lh_st lh_t;

typedef int (*lh_cb_t)(void *ctx, const void *keyptr, int keylen, const void *datptr, int datlen);

#if 0
lh_t *lh_create (void);
int   lh_insert (lh_t *h, const void *keyptr, int keylen, const void  *datptr, int  datlen, int override);
int   lh_lookup (lh_t *h, const void *keyptr, int keylen,       void **datptr, int *datlen);
int   lh_delete (lh_t *h, const void *keyptr, int keylen);
int   lh_apply  (lh_t *h, lh_cb_t cb, void *ctx);
int   lh_destroy(lh_t *h);
#endif

/* fixed size (number of pointers) of the directory and of each segment */
#define INITDIRSIZE  256 /* can be an arbitrary value */
#define SEGMENTSIZE  512 /* has to be a power of 2 for below arithmetic */

/* the borders for the hash table load */
#define MINLOADFCTR  1   /* should be between 0 and 1 */
#define MAXLOADFCTR  2   /* should be between 2 and 4 */

/* the per-element structure (keep as small as possible!) */
typedef struct element_st element_t;
struct element_st {
    element_t    *e_next;    /* pointer to next element in collision chain */
    unsigned long e_hash;    /* cached hash value of element (rehashing optimization) */
    void         *e_keyptr;  /* pointer to key (= also pointer to key+data memory chunk) */
    void         *e_datptr;  /* pointer to data in key+data memory chunk */
    void         *e_endptr;  /* pointer to end of key+data memory chunk */
};

/* the hash table segments */
typedef struct segment_st segment_t;
struct segment_st {
    element_t *s_element[SEGMENTSIZE]; /* array of pointers to elements */
}; 

/* the top-level hash table structure */
struct lh_st {
    unsigned int   h_p;         /* pointer to start of unsplit region */
    unsigned int   h_pmax;      /* pointer to end of unsplit region */
    int            h_slack;     /* grow/shrink indicator */
    unsigned       h_dirsize;   /* current size of directory */
    segment_t    **h_dir;       /* pointer to directory */
};

/* on-the-fly calculate index into directory and segment from virtual array index */
#define DIRINDEX(addr) (int)((addr) / SEGMENTSIZE)
#define SEGINDEX(addr) (int)((addr) % SEGMENTSIZE)

/* on-the-fly calculate lengths of key and data to reduce memory in element_t */
#define el_keylen(el) ((char *)((el)->e_datptr)-(char *)((el)->e_keyptr))
#define el_datlen(el) ((char *)((el)->e_endptr)-(char *)((el)->e_datptr))

/*
 * BJDDJ Hash Function (Bob Jenkins, Dr. Dobbs Journal):
 * This is a very complex but also very good hash function, as proposed
 * in the March'97 issue of Dr. Dobbs Journal (DDJ) by Bob Jenkins (see
 * http://burtleburtle.net/bob/hash/doobs.html for online version). He
 * showed that this hash function has both very good distribution and
 * performance and our own hash function comparison confirmed this. The
 * only difference to the original function of B.J. here is that our
 * version doesn't provide the `level' (= previous hash) argument for
 * consistency reasons with the other hash functions (i.e. same function
 * signature). It can be definetely recommended as a good general
 * purpuse hash function.
 */
static long 
lh_hash(
    register const unsigned char *k,
    register size_t length)
{
    register long a,b,c,len;

    /* some abbreviations */
#define ub4 long
#define mix(a,b,c) { \
        a -= b; a -= c; a ^= (c>>13); \
        b -= c; b -= a; b ^= (a<< 8); \
        c -= a; c -= b; c ^= (b>>13); \
        a -= b; a -= c; a ^= (c>>12); \
        b -= c; b -= a; b ^= (a<<16); \
        c -= a; c -= b; c ^= (b>> 5); \
        a -= b; a -= c; a ^= (c>> 3); \
        b -= c; b -= a; b ^= (a<<10); \
        c -= a; c -= b; c ^= (b>>15); \
    }

    /* setup the internal state */
    len = length;
    a = b = 0x9e3779b9;  /* the golden ratio; an arbitrary value */
    c = 0;

    /* handle most of the key */
    while (len >= 12) {
        a += (k[0] +((ub4)k[1]<<8) +((ub4)k[ 2]<<16) +((ub4)k[ 3]<<24));
        b += (k[4] +((ub4)k[5]<<8) +((ub4)k[ 6]<<16) +((ub4)k[ 7]<<24));
        c += (k[8] +((ub4)k[9]<<8) +((ub4)k[10]<<16) +((ub4)k[11]<<24));
        mix(a,b,c);
        k += 12; len -= 12;
    }

    /* handle the last 11 bytes */
    c += length;
    switch(len) { 
        /* all the case statements fall through */
        case 11: c+=((ub4)k[10]<<24);
        case 10: c+=((ub4)k[ 9]<<16);
        case 9 : c+=((ub4)k[ 8]<< 8);
        /* the first byte of c is reserved for the length */
        case 8 : b+=((ub4)k[ 7]<<24);
        case 7 : b+=((ub4)k[ 6]<<16);
        case 6 : b+=((ub4)k[ 5]<< 8);
        case 5 : b+=k[4];
        case 4 : a+=((ub4)k[ 3]<<24);
        case 3 : a+=((ub4)k[ 2]<<16);
        case 2 : a+=((ub4)k[ 1]<< 8);
        case 1 : a+=k[0];
        /* case 0: nothing left to add */
    }
    mix(a,b,c);

#undef ub4
#undef mix

    /* report the result */
    return c;
}

/* create the hash table structure */
static lh_t *lh_create(void)
{
    lh_t *h;

    /* allocate hash structure */
    if ((h = (lh_t *)malloc(sizeof(lh_t))) == NULL)
        return NULL;

    /* allocate and clear hash table directory */
    h->h_dirsize = INITDIRSIZE;
    if ((h->h_dir = (segment_t **)malloc(h->h_dirsize * sizeof(segment_t *))) == NULL) {
        free(h);
        return NULL;
    }
    memset(h->h_dir, 0, h->h_dirsize * sizeof(segment_t *));

    /* allocate and clear first segment of hash table array */
    if ((h->h_dir[0] = (segment_t *)malloc(sizeof(segment_t))) == NULL) {
        free(h->h_dir); 
        free(h);
        return NULL;
    }
    memset(h->h_dir[0], 0, sizeof(segment_t));

    /* initialize hash table control attributes */
    h->h_p     = 0;
    h->h_pmax  = SEGMENTSIZE;
    h->h_slack = SEGMENTSIZE * MAXLOADFCTR;

    return h;
}

/* expand the hash table */
static void lh_expand(lh_t *h)
{
    unsigned int pmax0;
    unsigned int newaddr;
    segment_t *seg;
    element_t **pelold;
    element_t *el, *headofold, *headofnew, *next;
    unsigned int n;
    
    /* calculate next new address */
    pmax0   = h->h_pmax;
    newaddr = pmax0 + h->h_p;

    /* have to enlarge directory? */
    if (h->h_dirsize <= DIRINDEX(newaddr)) {
        n = h->h_dirsize * sizeof(segment_t *);
        h->h_dirsize *= 2;
        if ((h->h_dir = (segment_t **)realloc(
                         h->h_dir, h->h_dirsize*sizeof(segment_t *))) == NULL)
             return;
        memset((char *)h->h_dir+n, 0, n);
    }
    
    /* have to create a new table segment? */
    if (SEGINDEX(newaddr) == 0) {
        if ((seg = (segment_t *)malloc(sizeof(segment_t))) == NULL)
            return;
        memset(seg, 0, sizeof(segment_t));
        h->h_dir[DIRINDEX(newaddr)] = seg;
    }

    /* locate P-element */
    pelold = &h->h_dir[DIRINDEX(h->h_p)]->s_element[SEGINDEX(h->h_p)];

    /* adjust the state variables */
    if (++(h->h_p) >= h->h_pmax) {
        h->h_pmax = (h->h_pmax << 1); /* == h->h_pmax * 2 */
        h->h_p = 0;
    }
    h->h_slack += MAXLOADFCTR;
    
    /* relocate and split between P-element new element */
    headofold = NULL;
    headofnew = NULL;
    for (el = *pelold; el != NULL; el = next) {
        next = el->e_next;
        if (el->e_hash & pmax0) {
            el->e_next = headofnew;
            headofnew  = el;
        } else {
            el->e_next = headofold;
            headofold  = el;
        }
    }
    *pelold = headofold;
    h->h_dir[DIRINDEX(newaddr)]->s_element[SEGINDEX(newaddr)] = headofnew;

    return;
}

/* shrink hash table */
static void lh_shrink(lh_t *h)
{
    segment_t *lastseg;
    element_t **pel;
    unsigned int oldlast;
    unsigned int dirsize;
    void *dir;

    /* calculate old last element */
    oldlast = h->h_p + h->h_pmax - 1;

    /* we cannot shrink below one segment */
    if (oldlast == 0)
        return;

    /* adjust the state variables */
    if (h->h_p == 0) {
        h->h_pmax = (h->h_pmax >> 1); /* == h->h_pmax / 2 */;
        h->h_p = h->h_pmax - 1;
    } else
        h->h_p--;
    h->h_slack -= MAXLOADFCTR;

    /* relocate the lost old last element to the end of the P-element */
    pel = &h->h_dir[DIRINDEX(h->h_p)]->s_element[SEGINDEX(h->h_p)];
    while (*pel != NULL)
        pel = &((*pel)->e_next);
    lastseg = h->h_dir[DIRINDEX(oldlast)];
    *pel = lastseg->s_element[SEGINDEX(oldlast)];
    lastseg->s_element[SEGINDEX(oldlast)] = NULL;

    /* if possible, deallocate the last segment */
    if (SEGINDEX(oldlast) == 0) {
        h->h_dir[DIRINDEX(oldlast)] = NULL;
        free(lastseg);
    }

    /* if possible, deallocate the end of the directory */
    if (h->h_dirsize > INITDIRSIZE && DIRINDEX(oldlast) < h->h_dirsize/2) {
        dirsize = (h->h_dirsize >> 1); /* == h->h_dirsize / 2 */
        if ((dir = (segment_t **)realloc(
                   h->h_dir, dirsize*sizeof(segment_t *))) != NULL) {
            h->h_dirsize = dirsize;
            h->h_dir = dir;
        }
    }
    return;
}

/* insert element into hash table */
static int lh_insert(lh_t *h, const void *keyptr, int keylen, const void *datptr, int datlen, int override)
{
    unsigned int hash, addr;
    element_t *el, **pel;
    int bFound;
    void *vp;

    /* argument consistency check */
    if (h == NULL || keyptr == NULL || keylen <= 0)
        return FALSE;

    /* calculate hash address */
    hash = lh_hash(keyptr, keylen);
    addr = hash % h->h_pmax; /* unsplit region */
    if (addr < h->h_p)
        addr = hash % (h->h_pmax << 1); /* split region */

    /* locate hash element's collision list */
    pel = &h->h_dir[DIRINDEX(addr)]->s_element[SEGINDEX(addr)];

    /* check whether element is already in the hash table */
    bFound = FALSE;
    for (el = *pel; el != NULL; el = el->e_next) {
        if (   el->e_hash == hash 
            && el_keylen(el) == keylen
            && memcmp(el->e_keyptr, keyptr, el_keylen(el)) == 0) {
            bFound = TRUE;
            break;
        }
    }

    /* only override on request */
    if (bFound && !override)
        return FALSE;

    /* create a duplicate of key and data */
    if ((vp = malloc(keylen+datlen)) == NULL)
        return FALSE;
    memmove(vp, keyptr, keylen);
    memmove((char *)vp+keylen, datptr, datlen);
    keyptr = vp;
    datptr = (char *)vp+keylen;

    if (bFound) {
        /* reuse existing element by freeing old contents */
        if (el->e_keyptr != NULL)
            free(el->e_keyptr);
    }
    else {
        /* allocate new element and chain into list */
        if ((el = (element_t *)malloc(sizeof(element_t))) == 0) {
            free(vp);
            return FALSE;
        }
        while (*pel != NULL)
            pel = &((*pel)->e_next);
        el->e_next = *pel;
        *pel = el;
    }

    /* insert contents into element structure */
    el->e_keyptr = (void *)keyptr;
    el->e_datptr = (void *)datptr;
    el->e_endptr = (char *)datptr+datlen;
    el->e_hash   = hash;

    /* do we need to expand the table now? */
    if (--(h->h_slack) < 0)
        lh_expand(h);

    return TRUE;
}

/* lookup an element in hash table */
static int lh_lookup(lh_t *h, const void *keyptr, int keylen, void **datptr, int *datlen)
{
    unsigned int hash, addr;
    element_t *el, **pel;

    /* argument consistency check */
    if (h == NULL || keyptr == NULL || keylen <= 0)
        return FALSE;

    /* calculate hash address */
    hash = lh_hash(keyptr, keylen);
    addr = hash % h->h_pmax; /* unsplit region */
    if (addr < h->h_p)
        addr = hash % (h->h_pmax << 1); /* split region */

    /* locate hash element collision list */
    pel = &h->h_dir[DIRINDEX(addr)]->s_element[SEGINDEX(addr)];

    /* search for element in collision list */
    for (el = *pel; el != NULL; el = el->e_next) {
        if (   el->e_hash == hash 
            && el_keylen(el) == keylen
            && memcmp(el->e_keyptr, keyptr, el_keylen(el)) == 0) {
            /* provide results */
            if (datptr != NULL)
                *datptr = el->e_datptr;
            if (datlen != NULL)
                *datlen = el_datlen(el);
            return TRUE;
        }
    }
    return FALSE;
}

/* delete an element in hash table */
static int lh_delete(lh_t *h, const void *keyptr, int keylen)
{
    unsigned int hash, addr;
    element_t *el, *lel, **pel;
    int rv;

    /* argument consistency check */
    if (h == NULL || keyptr == NULL || keylen <= 0)
        return FALSE;

    /* calculate hash address */
    hash = lh_hash(keyptr, keylen);
    addr = hash % h->h_pmax; /* unsplit region */
    if (addr < h->h_p)
        addr = hash % (h->h_pmax << 1); /* split region */

    /* locate hash element collision chain */
    pel = &h->h_dir[DIRINDEX(addr)]->s_element[SEGINDEX(addr)];

    /* search for element in collision chain */
    rv = FALSE;
    for (lel = NULL, el = *pel; el != NULL; lel = el, el = el->e_next) {
        if (   el->e_hash == hash 
            && el_keylen(el) == keylen
            && memcmp(el->e_keyptr, keyptr, el_keylen(el)) == 0) {
            /* free key+data memory chunk */
            if (el->e_keyptr != NULL)
                free(el->e_keyptr);
            /* remove element from collision chain */
            if (lel == NULL)
                *pel = el->e_next; 
            else
                lel->e_next = el->e_next;
            /* deallocate element structure */
            free(el);
            rv = TRUE;
            break;
        }
    }

    /* do we need to shrink the table now? */
    if (++(h->h_slack) > ((h->h_pmax + h->h_p) * (MAXLOADFCTR-MINLOADFCTR)))
        lh_shrink(h);

    return rv;
}

/* apply a callback for all elements in the hash table */
static int lh_apply(lh_t *h, lh_cb_t cb, void *ctx)
{
    element_t *el;
    unsigned int i, j;

    /* argument consistency check */
    if (h == NULL || cb == NULL)
        return FALSE;

    /* interate over all segment's entries */
    for (i = 0; i < h->h_dirsize; i++) {
        if (h->h_dir[i] == NULL)
            continue;
        /* interate over all collision chains */
        for (j = 0; j < SEGMENTSIZE; j++) {
            if (h->h_dir[i]->s_element[j] == NULL)
                continue;
            /* interate over all elements in collision chain */
            el = h->h_dir[i]->s_element[j];
            for (; el != NULL; el = el->e_next) {
                if (!cb(ctx, el->e_keyptr, el_keylen(el), el->e_datptr, el_datlen(el)))
                    return FALSE;
            }
        }
    }
    return TRUE;
}

/* destroy the whole hash table */
static int lh_destroy(lh_t *h)
{
    element_t *el, **pel;
    unsigned int i, j;

    /* argument consistency check */
    if (h == NULL)
        return FALSE;

    /* deallocate all segment's entries */
    for (i = 0; i < h->h_dirsize; i++) {
        if (h->h_dir[i] == NULL)
            continue;
        /* deallocate all collision chains */
        for (j = 0; j < SEGMENTSIZE; j++) {
            if (h->h_dir[i]->s_element[j] == NULL)
                continue;
            /* deallocate all elements in collision chain */
            pel = &h->h_dir[i]->s_element[j];
            for (el = *pel; el != NULL; el = el->e_next) {
                /* deallocate key+data memory chunk */
                if (el->e_keyptr != NULL)
                    free(el->e_keyptr);
            }
        }
        free(h->h_dir[i]);
    }

    /* deallocate hash table directory */
    free(h->h_dir);

    /* deallocate hash table top-level structure */
    free(h);

    return TRUE;
}

/*
**  ____               ____
**  ____ VALUE LIBRARY ____
**
*/

/* usually val_object_t.data is a pointer val_object_t.data.p, but VAL_INLINE
 * signals val_object_t.data is actual data val_object_t.data.[csilfd]
 */
enum {
    VAL_INLINE      = 1<<31
};

typedef struct {
    int type;
    union {
        val_t *v;
        void  *p;
        char   c;
        short  s;
        int    i;
        long   l;
        float  f;
        double d;
    } data;
    char *desc;
} val_object_t;

struct val_s {
    lh_t *lh;
};

static void *val_storage(val_object_t *obj)
{
    void *storage;

    if (obj->type & VAL_INLINE) {
        switch (obj->type & ~VAL_INLINE) {
            case VAL_TYPE_VAL:
                storage = &obj->data.v;
                break;
            case VAL_TYPE_PTR:
                storage = &obj->data.p;
                break;
            case VAL_TYPE_CHAR:
                storage = &obj->data.c;
                break;
            case VAL_TYPE_SHORT:
                storage = &obj->data.s;
                break;
            case VAL_TYPE_INT:
                storage = &obj->data.i;
                break;
            case VAL_TYPE_LONG:
                storage = &obj->data.l;
                break;
            case VAL_TYPE_FLOAT:
                storage = &obj->data.f;
                break;
            case VAL_TYPE_DOUBLE:
                storage = &obj->data.d;
                break;
            default:
                storage = NULL;
        }
    }
    else 
        storage = obj->data.p;
    return storage;
}

val_rc_t val_create(val_t **valp)
{
    val_t *val;

    if (valp == NULL)
        return VAL_ERR_ARG;
    if ((val = (val_t *)malloc(sizeof(val_t))) == NULL)
        return VAL_ERR_SYS;
    if ((val->lh = lh_create()) == NULL) {
        free(val);
        return VAL_ERR_SYS;
    }
    *valp = val;
    return VAL_OK;
}

static int (val_destroy_cb)(void *_ctx, const void *keyptr, int keylen, const void *datptr, int datlen)
{
    val_object_t *obj;
    
    obj = (val_object_t *)datptr;
    if (obj->desc != NULL)
        free(obj->desc);
    return TRUE;
}

val_rc_t val_destroy(val_t *val)
{
    if (val == NULL)
        return VAL_ERR_ARG;
    lh_apply(val->lh, val_destroy_cb, NULL);
    if (!lh_destroy(val->lh))
        return VAL_ERR_SYS;
    free(val);
    return VAL_OK;
}

val_rc_t val_reg(val_t *val, const char *name, int type, const char *desc, void *storage)
{
    val_object_t *obj;
    val_object_t newobj;
    const char *cp;
    val_t *child;

    if (val == NULL || name == NULL || type == 0)
        return VAL_ERR_ARG;



    if ((cp = strchr(name, '.')) != NULL) {
        if (!lh_lookup(val->lh, name, cp-name, (void **)&obj, NULL))
            return VAL_ERR_ARG;
        if (!(obj->type & VAL_TYPE_VAL))
            return VAL_ERR_USE;
        child = *(val_t **)(val_storage(obj));
        return val_reg(child, cp+1, type, desc, storage);
    }



    if (desc != NULL)
        newobj.desc = strdup(desc);
    else
        newobj.desc = NULL;
    if (storage == NULL) { 
        newobj.type   = (type | VAL_INLINE);
        newobj.data.l = 0;
    }
    else {
        newobj.type   = (type & ~VAL_INLINE);
        newobj.data.p = storage;
    }
    if (!lh_insert(val->lh, name, strlen(name), &newobj, sizeof(newobj), 1))
        return VAL_ERR_SYS;

    return VAL_OK;
}

val_rc_t val_vset(val_t *val, const char *name, va_list ap)
{
    val_object_t *obj;
    void *storage;
    const char *cp;
    val_t *child;

    if (val == NULL || name == NULL || ap == NULL)
        return VAL_ERR_ARG;
    if ((cp = strchr(name, '.')) != NULL) {
        if (!lh_lookup(val->lh, name, cp-name, (void **)&obj, NULL))
            return VAL_ERR_ARG;
        if (!(obj->type & VAL_TYPE_VAL))
            return VAL_ERR_USE;
        child = *(val_t **)(val_storage(obj));
        return val_vset(child, cp+1, ap);
    }
    if (!lh_lookup(val->lh, name, strlen(name), (void **)&obj, NULL))
        return VAL_ERR_ARG;
    storage = val_storage(obj);
    switch (obj->type & ~VAL_INLINE) {
        case VAL_TYPE_VAL:
            *(val_t **)storage = (val_t *)va_arg(ap, void *);
            break;
        case VAL_TYPE_PTR:
            *(char **)storage = (char *)va_arg(ap, void *);
            break;
        case VAL_TYPE_CHAR:
            *(char *)storage = (char)va_arg(ap, int);
            break;
        case VAL_TYPE_SHORT:
            *(short *)storage = (short)va_arg(ap, int);
            break;
        case VAL_TYPE_INT:
            *(int *)storage = (int)va_arg(ap, int);
            break;
        case VAL_TYPE_LONG:
            *(long *)storage = (long)va_arg(ap, long);
            break;
        case VAL_TYPE_FLOAT:
            *(float *)storage = (float)va_arg(ap, double);
            break;
        case VAL_TYPE_DOUBLE:
            *(double *)storage = (double)va_arg(ap, double);
            break;
    }
    return VAL_OK;
}

val_rc_t val_set(val_t *val, const char *name, ...)
{
    val_rc_t rc;
    va_list ap;

    if (val == NULL || name == NULL)
        return VAL_ERR_ARG;
    va_start(ap, name);
    rc = val_vset(val, name, ap);
    va_end(ap);
    return rc;
}

val_rc_t val_vget(val_t *val, const char *name, va_list ap)
{
    val_object_t *obj;
    void *storage;
    const char *cp;
    val_t *child;

    if (val == NULL || name == NULL || ap == NULL)
        return VAL_ERR_ARG;
    if ((cp = strchr(name, '.')) != NULL) {
        if (!lh_lookup(val->lh, name, cp-name, (void **)&obj, NULL))
            return VAL_ERR_ARG;
        if (!(obj->type & VAL_TYPE_VAL))
            return VAL_ERR_USE;
        child = *(val_t **)(val_storage(obj));
        return val_vget(child, cp+1, ap);
    }
    if (!lh_lookup(val->lh, name, strlen(name), (void **)&obj, NULL))
        return VAL_ERR_ARG;
    storage = val_storage(obj);
    switch (obj->type & ~VAL_INLINE) {
        case VAL_TYPE_VAL:
            *((val_t **)va_arg(ap, void *)) = *(val_t **)storage;
            break;
        case VAL_TYPE_PTR:
            *((char **)va_arg(ap, void *)) = *(char **)storage;
            break;
        case VAL_TYPE_CHAR:
            *((char *)va_arg(ap, int *)) = *(char *)storage;
            break;
        case VAL_TYPE_SHORT:
            *((short *)va_arg(ap, int *)) = *(short *)storage;
            break;
        case VAL_TYPE_INT:
            *((int *)va_arg(ap, int *)) = *(int *)storage;
            break;
        case VAL_TYPE_LONG:
            *((long *)va_arg(ap, long *)) = *(long *)storage;
            break;
        case VAL_TYPE_FLOAT:
            *((float *)va_arg(ap, double *)) = *(float *)storage;
            break;
        case VAL_TYPE_DOUBLE:
            *((double *)va_arg(ap, double *)) = *(double *)storage;
            break;
    }
    return VAL_OK;
}

val_rc_t val_get(val_t *val, const char *name, ...)
{
    val_rc_t rc;
    va_list ap;

    if (val == NULL || name == NULL)
        return VAL_ERR_ARG;
    va_start(ap, name);
    rc = val_vget(val, name, ap);
    va_end(ap);
    return rc;
}

#define VAL_MAXNAME 1024

typedef struct {
    val_t *val;
    char *name;
    int prefixlen;
    int depth;
    val_cb_t cb;
    void *ctx;
    val_rc_t rc;
} val_apply_ctx_t;

static val_rc_t val_apply_internal(val_t *, const char *, int, int, val_cb_t, void *);

static int (val_apply_cb)(void *_ctx, const void *keyptr, int keylen, const void *datptr, int datlen)
{
    val_apply_ctx_t *ctx = (val_apply_ctx_t *)_ctx;
    char name[VAL_MAXNAME+1];
    int prefixlen;

    /* on-the-fly create NUL-terminated name string */
    if ((strlen(ctx->name) + 1 + keylen) > VAL_MAXNAME) {
        ctx->rc = VAL_ERR_MEM;
        return FALSE;
    }
    if (strlen(ctx->name) > 0) {
        strcpy(name, ctx->name);
        strcat(name, ".");
        prefixlen = ctx->prefixlen + 1;
    }
    else {
        *name = '\0';
        prefixlen = ctx->prefixlen;
    }
    strncat(name, (char *)keyptr, keylen);
    if ((ctx->rc = val_apply_internal(ctx->val, name, prefixlen, ctx->depth, ctx->cb, ctx->ctx)) != VAL_OK)
        return FALSE;
    return TRUE;
}

static val_rc_t val_apply_internal(val_t *val, const char *name, int prefixlen, int depth, val_cb_t cb, void *ctx)
{
    val_object_t *obj;
    val_t *child;
    char *cp;
    val_rc_t rc;
    val_apply_ctx_t val_ctx;

    if (name[prefixlen] == '\0') {
        /* prefix="foo.bar.", remainder="" */
        val_ctx.val       = val;
        val_ctx.name      = (char *)name;
        val_ctx.prefixlen = prefixlen;
        val_ctx.depth     = depth;
        val_ctx.cb        = cb;
        val_ctx.ctx       = ctx;
        val_ctx.rc        = VAL_OK;
        if (!lh_apply(val->lh, val_apply_cb, &val_ctx))
            return VAL_ERR_SYS;
    }
    else {
        if ((cp = strchr(name+prefixlen, '.')) != NULL) {
            /* prefix="foo.bar.", remainder="quux.baz" */
            if (!lh_lookup(val->lh, name+prefixlen, cp-(name+prefixlen), (void **)&obj, NULL))
                return VAL_ERR_ARG;
            if (!(obj->type & VAL_TYPE_VAL))
                return VAL_ERR_USE;
            child = *(val_t **)(val_storage(obj));
            if (depth == 0)
                return VAL_OK;
            return val_apply_internal(child, name, cp-name+1, --depth, cb, ctx);
        }
        else {
            /* prefix="foo.bar.quux.", remainder="baz" */
            if (!lh_lookup(val->lh, name+prefixlen, strlen(name+prefixlen), (void **)&obj, NULL))
                return VAL_ERR_ARG;
            /* execute VAL callback */
            if ((rc = cb(ctx, name, (obj->type & ~VAL_INLINE), obj->desc, val_storage(obj))) != VAL_OK)
                return rc;
            if (obj->type & VAL_TYPE_VAL) {
                if (depth == 0)
                    return VAL_OK;
                child = *(val_t **)(val_storage(obj));
                return val_apply_internal(child, name, strlen(name), --depth, cb, ctx);
            }
        }
    }
    return VAL_OK;
}

val_rc_t val_apply(val_t *val, const char *name, int depth, val_cb_t cb, void *ctx)
{
    if (val == NULL || name == NULL || depth < 0 || cb == NULL)
        return VAL_ERR_ARG;

    return val_apply_internal(val, name, 0, depth, cb, ctx);
}


CVSTrac 2.0.1