/*
* Copyright 2001-2004 Brandon Long
* All Rights Reserved.
*
* ClearSilver Templating System
*
* This code is made available under the terms of the ClearSilver License.
* http://www.clearsilver.net/license.hdf
*
*/
#include "cs_config.h"
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <sys/stat.h>
#include "neo_misc.h"
#include "neo_err.h"
#include "neo_rand.h"
#include "neo_hdf.h"
#include "neo_str.h"
#include "neo_files.h"
#include "ulist.h"
/* Ok, in order to use the hash, we have to support n-len strings
* instead of null terminated strings (since in set_value and walk_hdf
* we are merely using part of the HDF name for lookup, and that might
* be a const, and we don't want the overhead of allocating/copying
* that data out...)
*
* Since HASH doesn't maintain any data placed in it, merely pointers to
* it, we use the HDF node itself as the key, and have specific
* comp/hash functions which just use the name/name_len as the key.
*/
static int hash_hdf_comp(const void *a, const void *b)
{
HDF *ha = (HDF *)a;
HDF *hb = (HDF *)b;
return (ha->name_len == hb->name_len) && !strncmp(ha->name, hb->name, ha->name_len);
}
static UINT32 hash_hdf_hash(const void *a)
{
HDF *ha = (HDF *)a;
return ne_crc((UINT8 *)(ha->name), ha->name_len);
}
static NEOERR *_alloc_hdf (HDF **hdf, const char *name, size_t nlen,
const char *value, int dup, int wf, HDF *top)
{
*hdf = calloc (1, sizeof (HDF));
if (*hdf == NULL)
{
return nerr_raise (NERR_NOMEM, "Unable to allocate memory for hdf element");
}
(*hdf)->top = top;
if (name != NULL)
{
(*hdf)->name_len = nlen;
(*hdf)->name = (char *) malloc (nlen + 1);
if ((*hdf)->name == NULL)
{
free((*hdf));
(*hdf) = NULL;
return nerr_raise (NERR_NOMEM,
"Unable to allocate memory for hdf element: %s", name);
}
strncpy((*hdf)->name, name, nlen);
(*hdf)->name[nlen] = '\0';
}
if (value != NULL)
{
if (dup)
{
(*hdf)->alloc_value = 1;
(*hdf)->value = strdup(value);
if ((*hdf)->value == NULL)
{
free((*hdf)->name);
free((*hdf));
(*hdf) = NULL;
return nerr_raise (NERR_NOMEM,
"Unable to allocate memory for hdf element %s", name);
}
}
else
{
(*hdf)->alloc_value = wf;
/* We're overriding the const of value here for the set_buf case
* where we overrode the char * to const char * earlier, since
* alloc_value actually keeps track of the const-ness for us */
(*hdf)->value = (char *)value;
}
}
return STATUS_OK;
}
static void _dealloc_hdf_attr(HDF_ATTR **attr)
{
HDF_ATTR *next;
while ((*attr) != NULL)
{
next = (*attr)->next;
if ((*attr)->key) free((*attr)->key);
if ((*attr)->value) free((*attr)->value);
free(*attr);
*attr = next;
}
*attr = NULL;
}
static void _dealloc_hdf (HDF **hdf)
{
HDF *myhdf = *hdf;
HDF *next = NULL;
if (myhdf == NULL) return;
if (myhdf->child != NULL)
_dealloc_hdf(&(myhdf->child));
/* This was easier recursively, but dangerous on long lists, so we
* walk it ourselves */
next = myhdf->next;
while (next != NULL)
{
myhdf->next = next->next;
next->next = NULL;
_dealloc_hdf(&next);
next = myhdf->next;
}
if (myhdf->name != NULL)
{
free (myhdf->name);
myhdf->name = NULL;
}
if (myhdf->value != NULL)
{
if (myhdf->alloc_value)
free (myhdf->value);
myhdf->value = NULL;
}
if (myhdf->attr != NULL)
{
_dealloc_hdf_attr(&(myhdf->attr));
}
if (myhdf->hash != NULL)
{
ne_hash_destroy(&myhdf->hash);
}
free(myhdf);
*hdf = NULL;
}
NEOERR* hdf_init (HDF **hdf)
{
NEOERR *err;
HDF *my_hdf;
*hdf = NULL;
err = nerr_init();
if (err != STATUS_OK)
return nerr_pass (err);
err = _alloc_hdf (&my_hdf, NULL, 0, NULL, 0, 0, NULL);
if (err != STATUS_OK)
return nerr_pass (err);
my_hdf->top = my_hdf;
*hdf = my_hdf;
return STATUS_OK;
}
void hdf_destroy (HDF **hdf)
{
if (*hdf == NULL) return;
if ((*hdf)->top == (*hdf))
{
_dealloc_hdf(hdf);
}
}
static int _walk_hdf (HDF *hdf, const char *name, HDF **node)
{
HDF *parent = NULL;
HDF *hp = hdf;
HDF hash_key;
int x = 0;
const char *s, *n;
int r;
*node = NULL;
if (hdf == NULL) return -1;
if (name == NULL || name[0] == '\0')
{
*node = hdf;
return 0;
}
if (hdf->link)
{
r = _walk_hdf (hdf->top, hdf->value, &hp);
if (r) return r;
if (hp)
{
parent = hp;
hp = hp->child;
}
}
else
{
parent = hdf;
hp = hdf->child;
}
if (hp == NULL)
{
return -1;
}
n = name;
s = strchr (n, '.');
x = (s == NULL) ? strlen(n) : s - n;
while (1)
{
if (parent && parent->hash)
{
hash_key.name = (char *)n;
hash_key.name_len = x;
hp = ne_hash_lookup(parent->hash, &hash_key);
}
else
{
while (hp != NULL)
{
if (hp->name && (x == hp->name_len) && !strncmp(hp->name, n, x))
{
break;
}
else
{
hp = hp->next;
}
}
}
if (hp == NULL)
{
return -1;
}
if (s == NULL) break;
if (hp->link)
{
r = _walk_hdf (hp->top, hp->value, &hp);
if (r) {
return r;
}
parent = hp;
hp = hp->child;
}
else
{
parent = hp;
hp = hp->child;
}
n = s + 1;
s = strchr (n, '.');
x = (s == NULL) ? strlen(n) : s - n;
}
if (hp->link)
{
return _walk_hdf (hp->top, hp->value, node);
}
*node = hp;
return 0;
}
int hdf_get_int_value (HDF *hdf, const char *name, int defval)
{
HDF *node;
int v;
char *n;
if ((_walk_hdf(hdf, name, &node) == 0) && (node->value != NULL))
{
v = strtol (node->value, &n, 10);
if (node->value == n) v = defval;
return v;
}
return defval;
}
/* This should return a const char *, but changing this would have big
* repurcussions for any C code using this function, so no change for now */
char* hdf_get_value (HDF *hdf, const char *name, const char *defval)
{
HDF *node;
if ((_walk_hdf(hdf, name, &node) == 0) && (node->value != NULL))
{
return node->value;
}
return (char *)defval;
}
char* hdf_get_valuevf (HDF *hdf, const char *namefmt, va_list ap)
{
HDF *node;
char *name;
name = vsprintf_alloc(namefmt, ap);
if (name == NULL) return NULL;
if ((_walk_hdf(hdf, name, &node) == 0) && (node->value != NULL))
{
free(name);
return node->value;
}
free(name);
return NULL;
}
char* hdf_get_valuef (HDF *hdf, const char *namefmt, ...)
{
char *val;
va_list ap;
va_start(ap, namefmt);
val = hdf_get_valuevf(hdf, namefmt, ap);
va_end(ap);
return val;
}
NEOERR* hdf_get_copy (HDF *hdf, const char *name, char **value,
const char *defval)
{
HDF *node;
if ((_walk_hdf(hdf, name, &node) == 0) && (node->value != NULL))
{
*value = strdup(node->value);
if (*value == NULL)
{
return nerr_raise (NERR_NOMEM, "Unable to allocate copy of %s", name);
}
}
else
{
if (defval == NULL)
*value = NULL;
else
{
*value = strdup(defval);
if (*value == NULL)
{
return nerr_raise (NERR_NOMEM, "Unable to allocate copy of %s", name);
}
}
}
return STATUS_OK;
}
HDF* hdf_get_obj (HDF *hdf, const char *name)
{
HDF *obj;
_walk_hdf(hdf, name, &obj);
return obj;
}
HDF* hdf_get_child (HDF *hdf, const char *name)
{
HDF *obj;
_walk_hdf(hdf, name, &obj);
if (obj != NULL) return obj->child;
return obj;
}
HDF_ATTR* hdf_get_attr (HDF *hdf, const char *name)
{
HDF *obj;
_walk_hdf(hdf, name, &obj);
if (obj != NULL) return obj->attr;
return NULL;
}
NEOERR* hdf_set_attr (HDF *hdf, const char *name, const char *key,
const char *value)
{
HDF *obj;
HDF_ATTR *attr, *last;
_walk_hdf(hdf, name, &obj);
if (obj == NULL)
return nerr_raise(NERR_ASSERT, "Unable to set attribute on none existant node");
if (obj->attr != NULL)
{
attr = obj->attr;
last = attr;
while (attr != NULL)
{
if (!strcmp(attr->key, key))
{
if (attr->value) free(attr->value);
/* a set of NULL deletes the attr */
if (value == NULL)
{
if (attr == obj->attr)
obj->attr = attr->next;
else
last->next = attr->next;
free(attr->key);
free(attr);
return STATUS_OK;
}
attr->value = strdup(value);
if (attr->value == NULL)
return nerr_raise(NERR_NOMEM, "Unable to set attr %s to %s", key, value);
return STATUS_OK;
}
last = attr;
attr = attr->next;
}
last->next = (HDF_ATTR *) calloc(1, sizeof(HDF_ATTR));
if (last->next == NULL)
return nerr_raise(NERR_NOMEM, "Unable to set attr %s to %s", key, value);
attr = last->next;
}
else
{
if (value == NULL) return STATUS_OK;
obj->attr = (HDF_ATTR *) calloc(1, sizeof(HDF_ATTR));
if (obj->attr == NULL)
return nerr_raise(NERR_NOMEM, "Unable to set attr %s to %s", key, value);
attr = obj->attr;
}
attr->key = strdup(key);
attr->value = strdup(value);
if (attr->key == NULL || attr->value == NULL)
return nerr_raise(NERR_NOMEM, "Unable to set attr %s to %s", key, value);
return STATUS_OK;
}
HDF* hdf_obj_child (HDF *hdf)
{
HDF *obj;
if (hdf == NULL) return NULL;
if (hdf->link)
{
if (_walk_hdf(hdf->top, hdf->value, &obj))
return NULL;
return obj->child;
}
return hdf->child;
}
HDF* hdf_obj_next (HDF *hdf)
{
if (hdf == NULL) return NULL;
return hdf->next;
}
HDF* hdf_obj_top (HDF *hdf)
{
if (hdf == NULL) return NULL;
return hdf->top;
}
HDF_ATTR* hdf_obj_attr (HDF *hdf)
{
if (hdf == NULL) return NULL;
return hdf->attr;
}
char* hdf_obj_name (HDF *hdf)
{
if (hdf == NULL) return NULL;
return hdf->name;
}
char* hdf_obj_value (HDF *hdf)
{
int count = 0;
if (hdf == NULL) return NULL;
while (hdf->link && count < 100)
{
if (_walk_hdf (hdf->top, hdf->value, &hdf))
return NULL;
count++;
}
return hdf->value;
}
void _merge_attr (HDF_ATTR *dest, HDF_ATTR *src)
{
HDF_ATTR *da, *ld;
HDF_ATTR *sa, *ls;
BOOL found;
sa = src;
ls = src;
while (sa != NULL)
{
da = dest;
ld = da;
found = 0;
while (da != NULL)
{
if (!strcmp(da->key, sa->key))
{
if (da->value) free(da->value);
da->value = sa->value;
sa->value = NULL;
found = 1;
break;
}
ld = da;
da = da->next;
}
if (!found)
{
ld->next = sa;
ls->next = sa->next;
if (src == sa) src = sa->next;
ld->next->next = NULL;
sa = ls->next;
}
else
{
ls = sa;
sa = sa->next;
}
}
_dealloc_hdf_attr(&src);
}
NEOERR* _hdf_hash_level(HDF *hdf)
{
NEOERR *err;
HDF *child;
err = ne_hash_init(&(hdf->hash), hash_hdf_hash, hash_hdf_comp);
if (err) return nerr_pass(err);
child = hdf->child;
while (child)
{
err = ne_hash_insert(hdf->hash, child, child);
if (err) return nerr_pass(err);
child = child->next;
}
return STATUS_OK;
}
static NEOERR* _set_value (HDF *hdf, const char *name, const char *value,
int dup, int wf, int link, HDF_ATTR *attr,
HDF **set_node)
{
NEOERR *err;
HDF *hn, *hp, *hs;
HDF hash_key;
int x = 0;
const char *s = name;
const char *n = name;
int count = 0;
if (set_node != NULL) *set_node = NULL;
if (hdf == NULL)
{
return nerr_raise(NERR_ASSERT, "Unable to set %s on NULL hdf", name);
}
/* HACK: allow setting of this node by passing an empty name */
if (name == NULL || name[0] == '\0')
{
/* handle setting attr first */
if (hdf->attr == NULL)
{
hdf->attr = attr;
}
else
{
_merge_attr(hdf->attr, attr);
}
/* if we're setting ourselves to ourselves... */
if (hdf->value == value)
{
if (set_node != NULL) *set_node = hdf;
return STATUS_OK;
}
if (hdf->alloc_value)
{
free(hdf->value);
hdf->value = NULL;
}
if (value == NULL)
{
hdf->alloc_value = 0;
hdf->value = NULL;
}
else if (dup)
{
hdf->alloc_value = 1;
hdf->value = strdup(value);
if (hdf->value == NULL)
return nerr_raise (NERR_NOMEM, "Unable to duplicate value %s for %s",
value, name);
}
else
{
hdf->alloc_value = wf;
hdf->value = (char *)value;
}
if (set_node != NULL) *set_node = hdf;
return STATUS_OK;
}
n = name;
s = strchr (n, '.');
x = (s != NULL) ? s - n : strlen(n);
if (x == 0)
{
return nerr_raise(NERR_ASSERT, "Unable to set Empty component %s", name);
}
if (hdf->link)
{
char *new_name = (char *) malloc(strlen(hdf->value) + 1 + strlen(name) + 1);
if (new_name == NULL)
{
return nerr_raise(NERR_NOMEM, "Unable to allocate memory");
}
strcpy(new_name, hdf->value);
strcat(new_name, ".");
strcat(new_name, name);
err = _set_value (hdf->top, new_name, value, dup, wf, link, attr, set_node);
free(new_name);
return nerr_pass(err);
}
else
{
hn = hdf;
}
while (1)
{
/* examine cache to see if we have a match */
count = 0;
hp = hn->last_hp;
hs = hn->last_hs;
if ((hs == NULL && hp == hn->child) || (hs && hs->next == hp))
{
if (hp && hp->name && (x == hp->name_len) && !strncmp (hp->name, n, x))
{
goto skip_search;
}
}
hp = hn->child;
hs = NULL;
/* Look for a matching node at this level */
if (hn->hash != NULL)
{
hash_key.name = (char *)n;
hash_key.name_len = x;
hp = ne_hash_lookup(hn->hash, &hash_key);
hs = hn->last_child;
}
else
{
while (hp != NULL)
{
if (hp->name && (x == hp->name_len) && !strncmp(hp->name, n, x))
{
break;
}
hs = hp;
hp = hp->next;
count++;
}
}
/* save in cache any value we found */
if (hp) {
hn->last_hp = hp;
hn->last_hs = hs;
}
skip_search:
if (hp == NULL)
{
/* If there was no matching node at this level, we need to
* allocate an intersitial node (or the actual node if we're
* at the last part of the HDF name) */
if (s != NULL)
{
/* intersitial */
err = _alloc_hdf (&hp, n, x, NULL, 0, 0, hdf->top);
}
else
{
err = _alloc_hdf (&hp, n, x, value, dup, wf, hdf->top);
if (link) hp->link = 1;
else hp->link = 0;
hp->attr = attr;
}
if (err != STATUS_OK)
return nerr_pass (err);
if (hn->child == NULL)
hn->child = hp;
else
hs->next = hp;
hn->last_child = hp;
/* This is the point at which we convert to a hash table
* at this level, if we're over the count */
if (count > FORCE_HASH_AT && hn->hash == NULL)
{
err = _hdf_hash_level(hn);
if (err) return nerr_pass(err);
}
else if (hn->hash != NULL)
{
err = ne_hash_insert(hn->hash, hp, hp);
if (err) return nerr_pass(err);
}
}
else if (s == NULL)
{
/* If there is a matching node and we're at the end of the HDF
* name, then we update the value of the node */
/* handle setting attr first */
if (hp->attr == NULL)
{
hp->attr = attr;
}
else
{
_merge_attr(hp->attr, attr);
}
if (hp->value != value)
{
if (hp->alloc_value)
{
free(hp->value);
hp->value = NULL;
}
if (value == NULL)
{
hp->alloc_value = 0;
hp->value = NULL;
}
else if (dup)
{
hp->alloc_value = 1;
hp->value = strdup(value);
if (hp->value == NULL)
return nerr_raise (NERR_NOMEM, "Unable to duplicate value %s for %s",
value, name);
}
else
{
hp->alloc_value = wf;
hp->value = (char *)value;
}
}
if (link) hp->link = 1;
else hp->link = 0;
}
else if (hp->link)
{
char *new_name = (char *) malloc(strlen(hp->value) + strlen(s) + 1);
if (new_name == NULL)
{
return nerr_raise(NERR_NOMEM, "Unable to allocate memory");
}
strcpy(new_name, hp->value);
strcat(new_name, s);
err = _set_value (hdf->top, new_name, value, dup, wf, link, attr, set_node);
free(new_name);
return nerr_pass(err);
}
/* At this point, we're done if there is not more HDF name space to
* traverse */
if (s == NULL)
break;
/* Otherwise, we need to find the next part of the namespace */
n = s + 1;
s = strchr (n, '.');
x = (s != NULL) ? s - n : strlen(n);
if (x == 0)
{
return nerr_raise(NERR_ASSERT, "Unable to set Empty component %s", name);
}
hn = hp;
}
if (set_node != NULL) *set_node = hp;
return STATUS_OK;
}
NEOERR* hdf_set_value (HDF *hdf, const char *name, const char *value)
{
return nerr_pass(_set_value (hdf, name, value, 1, 1, 0, NULL, NULL));
}
NEOERR* hdf_set_value_attr (HDF *hdf, const char *name, const char *value,
HDF_ATTR *attr)
{
return nerr_pass(_set_value (hdf, name, value, 1, 1, 0, attr, NULL));
}
NEOERR* hdf_set_symlink (HDF *hdf, const char *src, const char *dest)
{
return nerr_pass(_set_value (hdf, src, dest, 1, 1, 1, NULL, NULL));
}
NEOERR* hdf_set_int_value (HDF *hdf, const char *name, int value)
{
char buf[256];
snprintf (buf, sizeof(buf), "%d", value);
return nerr_pass(_set_value (hdf, name, buf, 1, 1, 0, NULL, NULL));
}
NEOERR* hdf_set_buf (HDF *hdf, const char *name, char *value)
{
return nerr_pass(_set_value (hdf, name, value, 0, 1, 0, NULL, NULL));
}
NEOERR* hdf_set_copy (HDF *hdf, const char *dest, const char *src)
{
HDF *node;
if ((_walk_hdf(hdf, src, &node) == 0) && (node->value != NULL))
{
return nerr_pass(_set_value (hdf, dest, node->value, 0, 0, 0, NULL, NULL));
}
return nerr_raise (NERR_NOT_FOUND, "Unable to find %s", src);
}
NEOERR* hdf_set_valuevf (HDF *hdf, const char *fmt, va_list ap)
{
NEOERR *err;
char *k;
char *v;
k = vsprintf_alloc(fmt, ap);
if (k == NULL)
{
return nerr_raise(NERR_NOMEM, "Unable to allocate memory for format string");
}
v = strchr(k, '=');
if (v == NULL)
{
err = nerr_raise(NERR_ASSERT, "No equals found: %s", k);
free(k);
return err;
}
*v++ = '\0';
err = hdf_set_value(hdf, k, v);
free(k);
return nerr_pass(err);
}
NEOERR* hdf_set_valuef (HDF *hdf, const char *fmt, ...)
{
NEOERR *err;
va_list ap;
va_start(ap, fmt);
err = hdf_set_valuevf(hdf, fmt, ap);
va_end(ap);
return nerr_pass(err);
}
NEOERR* hdf_get_node (HDF *hdf, const char *name, HDF **ret)
{
_walk_hdf(hdf, name, ret);
if (*ret == NULL)
{
return nerr_pass(_set_value (hdf, name, NULL, 0, 1, 0, NULL, ret));
}
return STATUS_OK;
}
/* Ok, this version avoids the bubble sort by walking the level once to
* load them all into a ULIST, qsort'ing the list, and then dumping them
* back out... */
NEOERR *hdf_sort_obj (HDF *h, int (*compareFunc)(const void *, const void *))
{
NEOERR *err = STATUS_OK;
ULIST *level = NULL;
HDF *p, *c;
int x;
if (h == NULL) return STATUS_OK;
c = h->child;
if (c == NULL) return STATUS_OK;
do {
err = uListInit(&level, 40, 0);
if (err) return nerr_pass(err);
for (p = c; p; p = p->next) {
err = uListAppend(level, p);
if (err) break;
}
err = uListSort(level, compareFunc);
if (err) break;
uListGet(level, 0, (void *)&c);
h->child = c;
for (x = 1; x < uListLength(level); x++)
{
uListGet(level, x, (void *)&p);
c->next = p;
p->next = NULL;
c = p;
}
h->last_child = c;
} while (0);
uListDestroy(&level, 0);
return nerr_pass(err);
}
NEOERR* hdf_remove_tree (HDF *hdf, const char *name)
{
HDF *hp = hdf;
HDF *lp = NULL, *ln = NULL; /* last parent, last node */
int x = 0;
const char *s = name;
const char *n = name;
if (hdf == NULL) return STATUS_OK;
hp = hdf->child;
if (hp == NULL)
{
return STATUS_OK;
}
lp = hdf;
ln = NULL;
n = name;
s = strchr (n, '.');
x = (s == NULL) ? strlen(n) : s - n;
while (1)
{
while (hp != NULL)
{
if (hp->name && (x == hp->name_len) && !strncmp(hp->name, n, x))
{
break;
}
else
{
ln = hp;
hp = hp->next;
}
}
if (hp == NULL)
{
return STATUS_OK;
}
if (s == NULL) break;
lp = hp;
ln = NULL;
hp = hp->child;
n = s + 1;
s = strchr (n, '.');
x = (s == NULL) ? strlen(n) : s - n;
}
if (lp->hash != NULL)
{
ne_hash_remove(lp->hash, hp);
}
if (ln)
{
ln->next = hp->next;
/* check to see if we are the last parent's last_child, if so
* repoint so hash table inserts will go to the right place */
if (hp == lp->last_child)
lp->last_child = ln;
hp->next = NULL;
}
else
{
lp->child = hp->next;
hp->next = NULL;
}
_dealloc_hdf (&hp);
return STATUS_OK;
}
static NEOERR * _copy_attr (HDF_ATTR **dest, HDF_ATTR *src)
{
HDF_ATTR *copy, *last = NULL;
*dest = NULL;
while (src != NULL)
{
copy = (HDF_ATTR *)malloc(sizeof(HDF_ATTR));
if (copy == NULL)
{
_dealloc_hdf_attr(dest);
return nerr_raise(NERR_NOMEM, "Unable to allocate copy of HDF_ATTR");
}
copy->key = strdup(src->key);
copy->value = strdup(src->value);
copy->next = NULL;
if ((copy->key == NULL) || (copy->value == NULL))
{
_dealloc_hdf_attr(dest);
return nerr_raise(NERR_NOMEM, "Unable to allocate copy of HDF_ATTR");
}
if (last) {
last->next = copy;
}
else
{
*dest = copy;
}
last = copy;
src = src->next;
}
return STATUS_OK;
}
static NEOERR * _copy_nodes (HDF *dest, HDF *src)
{
NEOERR *err = STATUS_OK;
HDF *dt, *st;
HDF_ATTR *attr_copy;
st = src->child;
while (st != NULL)
{
err = _copy_attr(&attr_copy, st->attr);
if (err) return nerr_pass(err);
err = _set_value(dest, st->name, st->value, 1, 1, 0, attr_copy, &dt);
if (err) {
_dealloc_hdf_attr(&attr_copy);
return nerr_pass(err);
}
if (src->child)
{
err = _copy_nodes (dt, st);
if (err) return nerr_pass(err);
}
st = st->next;
}
return STATUS_OK;
}
NEOERR* hdf_copy (HDF *dest, const char *name, HDF *src)
{
NEOERR *err;
HDF *node;
if (_walk_hdf(dest, name, &node) == -1)
{
err = _set_value (dest, name, NULL, 0, 0, 0, NULL, &node);
if (err) return nerr_pass (err);
}
return nerr_pass (_copy_nodes (node, src));
}
/* BUG: currently, this only prints something if there is a value...
* but we now allow attributes on nodes with no value... */
static void gen_ml_break(char *ml, size_t len)
{
int nlen;
int x = 0;
ml[x++] = '\n';
nlen = 2 + neo_rand(len-5);
if (nlen == 0)
{
nlen = len / 2;
}
while (nlen)
{
ml[x++] = ('A' + neo_rand(26));
nlen--;
}
ml[x++] = '\n';
ml[x] = '\0';
}
typedef NEOERR *(*DUMPF_CB)(void *rock, const char *fmt, ...);
static NEOERR *_fp_dump_cb (void *rock, const char *fmt, ...)
{
FILE *fp = (FILE *)rock;
va_list ap;
va_start (ap, fmt);
vfprintf(fp, fmt, ap);
va_end(ap);
return STATUS_OK;
}
static NEOERR *_string_dump_cb (void *rock, const char *fmt, ...)
{
NEOERR *err;
STRING *str = (STRING *)rock;
va_list ap;
va_start (ap, fmt);
err = string_appendvf(str, fmt, ap);
va_end(ap);
return nerr_pass(err);
}
#define DUMP_TYPE_DOTTED 0
#define DUMP_TYPE_COMPACT 1
#define DUMP_TYPE_PRETTY 2
static NEOERR* hdf_dump_cb(HDF *hdf, const char *prefix, int dtype, int lvl,
void *rock, DUMPF_CB dump_cbf)
{
NEOERR *err;
char *p, op;
char ml[10] = "\nEOM\n";
int ml_len = strlen(ml);
char whsp[256] = "";
if (dtype == DUMP_TYPE_PRETTY)
{
memset(whsp, ' ', 256);
if (lvl > 127)
lvl = 127;
whsp[lvl*2] = '\0';
}
if (hdf != NULL) hdf = hdf->child;
while (hdf != NULL)
{
op = '=';
if (hdf->value)
{
if (hdf->link) op = ':';
if (prefix && (dtype == DUMP_TYPE_DOTTED))
{
err = dump_cbf(rock, "%s.%s", prefix, hdf->name);
}
else
{
err = dump_cbf(rock, "%s%s", whsp, hdf->name);
}
if (err) return nerr_pass (err);
if (hdf->attr)
{
HDF_ATTR *attr = hdf->attr;
char *v = NULL;
err = dump_cbf(rock, " [");
if (err) return nerr_pass(err);
while (attr != NULL)
{
if (attr->value == NULL || !strcmp(attr->value, "1"))
err = dump_cbf(rock, "%s", attr->key);
else
{
v = repr_string_alloc(attr->value);
if (v == NULL)
return nerr_raise(NERR_NOMEM, "Unable to repr attr %s value %s", attr->key, attr->value);
err = dump_cbf(rock, "%s=%s", attr->key, v);
free(v);
}
if (err) return nerr_pass(err);
if (attr->next)
{
err = dump_cbf(rock, ", ");
if (err) return nerr_pass(err);
}
attr = attr->next;
}
err = dump_cbf(rock, "] ");
if (err) return nerr_pass(err);
}
if (strchr (hdf->value, '\n'))
{
int vlen = strlen(hdf->value);
while (strstr(hdf->value, ml) || ((vlen > ml_len) && !strncmp(hdf->value + vlen - ml_len + 1, ml, strlen(ml) - 1)))
{
gen_ml_break(ml, sizeof(ml));
ml_len = strlen(ml);
}
if (hdf->value[strlen(hdf->value)-1] != '\n')
err = dump_cbf(rock, " << %s%s%s", ml+1, hdf->value, ml);
else
err = dump_cbf(rock, " << %s%s%s", ml+1, hdf->value, ml+1);
}
else
{
err = dump_cbf(rock, " %c %s\n", op, hdf->value);
}
if (err) return nerr_pass (err);
}
if (hdf->child)
{
if (prefix && (dtype == DUMP_TYPE_DOTTED))
{
p = (char *) malloc (strlen(hdf->name) + strlen(prefix) + 2);
sprintf (p, "%s.%s", prefix, hdf->name);
err = hdf_dump_cb (hdf, p, dtype, lvl+1, rock, dump_cbf);
free(p);
}
else
{
if (hdf->name && (dtype != DUMP_TYPE_DOTTED))
{
err = dump_cbf(rock, "%s%s {\n", whsp, hdf->name);
if (err) return nerr_pass (err);
err = hdf_dump_cb (hdf, hdf->name, dtype, lvl+1, rock, dump_cbf);
if (err) return nerr_pass (err);
err = dump_cbf(rock, "%s}\n", whsp);
}
else
{
err = hdf_dump_cb (hdf, hdf->name, dtype, lvl+1, rock, dump_cbf);
}
}
if (err) return nerr_pass (err);
}
hdf = hdf->next;
}
return STATUS_OK;
}
NEOERR* hdf_dump_str (HDF *hdf, const char *prefix, int dtype, STRING *str)
{
return nerr_pass(hdf_dump_cb(hdf, prefix, dtype, 0, str, _string_dump_cb));
}
NEOERR* hdf_dump(HDF *hdf, const char *prefix)
{
return nerr_pass(hdf_dump_cb(hdf, prefix, DUMP_TYPE_DOTTED, 0, stdout, _fp_dump_cb));
}
NEOERR* hdf_dump_format (HDF *hdf, int lvl, FILE *fp)
{
return nerr_pass(hdf_dump_cb(hdf, "", DUMP_TYPE_PRETTY, 0, fp, _fp_dump_cb));
}
NEOERR *hdf_write_file (HDF *hdf, const char *path)
{
NEOERR *err;
FILE *fp;
fp = fopen(path, "w");
if (fp == NULL)
return nerr_raise_errno (NERR_IO, "Unable to open %s for writing", path);
err = hdf_dump_format (hdf, 0, fp);
fclose (fp);
if (err)
{
unlink(path);
}
return nerr_pass(err);
}
NEOERR *hdf_write_file_atomic (HDF *hdf, const char *path)
{
NEOERR *err;
FILE *fp;
char tpath[_POSIX_PATH_MAX];
static int count = 0;
snprintf(tpath, sizeof(tpath), "%s.%5.5f.%d", path, ne_timef(), count++);
fp = fopen(tpath, "w");
if (fp == NULL)
return nerr_raise_errno (NERR_IO, "Unable to open %s for writing", tpath);
err = hdf_dump_format (hdf, 0, fp);
fclose (fp);
if (err)
{
unlink(tpath);
return nerr_pass(err);
}
if (rename(tpath, path) == -1)
{
unlink (tpath);
return nerr_raise_errno (NERR_IO, "Unable to rename file %s to %s",
tpath, path);
}
return STATUS_OK;
}
NEOERR *hdf_write_string (HDF *hdf, char **s)
{
STRING str;
NEOERR *err;
*s = NULL;
string_init (&str);
err = hdf_dump_str (hdf, NULL, 1, &str);
if (err)
{
string_clear (&str);
return nerr_pass(err);
}
if (str.buf == NULL)
{
*s = strdup("");
if (*s == NULL) return nerr_raise(NERR_NOMEM, "Unable to allocate empty string");
}
else
{
*s = str.buf;
}
return STATUS_OK;
}
#define SKIPWS(s) while (*s && isspace(*s)) s++;
static int _copy_line (const char **s, char *buf, size_t buf_len)
{
int x = 0;
const char *st = *s;
while (*st && x < buf_len-1)
{
buf[x++] = *st;
if (*st++ == '\n') break;
}
buf[x] = '\0';
*s = st;
return x;
}
/* Copy the characters in the file (up to the next newline) into line
* and advance s to the next line */
static NEOERR *_copy_line_advance(const char **s, STRING *line)
{
NEOERR *err;
int x = 0;
const char *st = *s;
const char *nl;
nl = strchr(st, '\n');
if (nl == NULL)
{
x = strlen(st);
err = string_appendn(line, st, x);
if (err) return nerr_pass(err);
*s = st + x;
}
else
{
x = nl - st;
err = string_appendn(line, st, x);
if (err) return nerr_pass(err);
*s = nl + 1;
}
return STATUS_OK;
}
char *_strndup(const char *s, int len) {
int x;
char *dup;
if (s == NULL) return NULL;
dup = (char *) malloc(len+1);
if (dup == NULL) return NULL;
for (x = 0; x < len && s[x]; x++)
{
dup[x] = s[x];
}
dup[x] = '\0';
dup[len] = '\0';
return dup;
}
/* attributes are of the form [key1, key2, key3=value, key4="repr"] */
static NEOERR* parse_attr(char **str, HDF_ATTR **attr)
{
NEOERR *err = STATUS_OK;
char *s = *str;
char *k, *v;
int k_l, v_l;
STRING buf;
char c;
HDF_ATTR *ha, *hal = NULL;
*attr = NULL;
string_init(&buf);
while (*s && *s != ']')
{
k = s;
k_l = 0;
v = NULL;
v_l = 0;
while (*s && isalnum(*s)) s++;
k_l = s-k;
if (*s == '\0' || k_l == 0)
{
_dealloc_hdf_attr(attr);
return nerr_raise(NERR_PARSE, "Misformed attribute specification: %s", *str);
}
SKIPWS(s);
if (*s == '=')
{
s++;
SKIPWS(s);
if (*s == '"')
{
s++;
while (*s && *s != '"')
{
if (*s == '\\')
{
if (isdigit(*(s+1)))
{
s++;
c = *s - '0';
if (isdigit(*(s+1)))
{
s++;
c = (c * 8) + (*s - '0');
if (isdigit(*(s+1)))
{
s++;
c = (c * 8) + (*s - '0');
}
}
}
else
{
s++;
if (*s == 'n') c = '\n';
else if (*s == 't') c = '\t';
else if (*s == 'r') c = '\r';
else c = *s;
}
err = string_append_char(&buf, c);
}
else
{
err = string_append_char(&buf, *s);
}
if (err)
{
string_clear(&buf);
_dealloc_hdf_attr(attr);
return nerr_pass(err);
}
s++;
}
if (*s == '\0')
{
_dealloc_hdf_attr(attr);
string_clear(&buf);
return nerr_raise(NERR_PARSE, "Misformed attribute specification: %s", *str);
}
s++;
v = buf.buf;
v_l = buf.len;
}
else
{
v = s;
while (*s && *s != ' ' && *s != ',' && *s != ']') s++;
if (*s == '\0')
{
_dealloc_hdf_attr(attr);
return nerr_raise(NERR_PARSE, "Misformed attribute specification: %s", *str);
}
v_l = s-v;
}
}
else
{
v = "1";
}
ha = (HDF_ATTR*) calloc (1, sizeof(HDF_ATTR));
if (ha == NULL)
{
_dealloc_hdf_attr(attr);
string_clear(&buf);
return nerr_raise(NERR_NOMEM, "Unable to load attributes: %s", s);
}
if (*attr == NULL) *attr = ha;
ha->key = _strndup(k, k_l);
if (v)
ha->value = _strndup(v, v_l);
else
ha->value = strdup("");
if (ha->key == NULL || ha->value == NULL)
{
_dealloc_hdf_attr(attr);
string_clear(&buf);
return nerr_raise(NERR_NOMEM, "Unable to load attributes: %s", s);
}
if (hal != NULL) hal->next = ha;
hal = ha;
string_clear(&buf);
SKIPWS(s);
if (*s == ',')
{
s++;
SKIPWS(s);
}
}
if (*s == '\0')
{
_dealloc_hdf_attr(attr);
return nerr_raise(NERR_PARSE, "Misformed attribute specification: %s", *str);
}
*str = s+1;
return STATUS_OK;
}
#define INCLUDE_ERROR 0
#define INCLUDE_IGNORE 1
#define INCLUDE_FILE 2
static NEOERR* _hdf_read_string (HDF *hdf, const char **str, STRING *line,
const char *path, int *lineno, int include_handle)
{
NEOERR *err;
HDF *lower;
char *s;
char *name, *value;
HDF_ATTR *attr = NULL;
while (**str != '\0')
{
/* Reset string length, but don't free the reserved buffer */
line->len = 0;
err = _copy_line_advance(str, line);
if (err) return nerr_pass(err);
attr = NULL;
(*lineno)++;
s = line->buf;
SKIPWS(s);
if (!strncmp(s, "#include ", 9))
{
if (include_handle == INCLUDE_ERROR)
{
return nerr_raise (NERR_PARSE,
"[%d]: #include not supported in string parse",
*lineno);
}
else if (include_handle == INCLUDE_FILE)
{
int l;
s += 9;
name = neos_strip(s);
l = strlen(name);
if (name[0] == '"' && name[l-1] == '"')
{
name[l-1] = '\0';
name++;
}
err = hdf_read_file(hdf, name);
if (err != STATUS_OK)
{
return nerr_pass_ctx(err, "In file %s:%d", path, *lineno);
}
}
}
else if (s[0] == '#')
{
/* comment: pass */
}
else if (s[0] == '}') /* up */
{
s = neos_strip(s);
if (strcmp(s, "}"))
{
err = nerr_raise(NERR_PARSE,
"[%s:%d] Trailing garbage on line following }: %s", path, *lineno,
line->buf);
return err;
}
return STATUS_OK;
}
else if (s[0])
{
/* Valid hdf name is [0-9a-zA-Z_.]+ */
name = s;
while (*s && (isalnum(*s) || *s == '_' || *s == '.')) s++;
SKIPWS(s);
if (s[0] == '[') /* attributes */
{
*s = '\0';
name = neos_strip(name);
s++;
err = parse_attr(&s, &attr);
if (err)
{
return nerr_pass_ctx(err, "In file %s:%d", path, *lineno);
}
SKIPWS(s);
}
if (s[0] == '=') /* assignment */
{
*s = '\0';
name = neos_strip(name);
s++;
value = neos_strip(s);
err = _set_value (hdf, name, value, 1, 1, 0, attr, NULL);
if (err != STATUS_OK)
{
return nerr_pass_ctx(err, "In file %s:%d", path, *lineno);
}
}
else if (s[0] == ':' && s[1] == '=') /* copy */
{
*s = '\0';
name = neos_strip(name);
s+=2;
value = neos_strip(s);
value = hdf_get_value(hdf->top, value, "");
err = _set_value (hdf, name, value, 1, 1, 0, attr, NULL);
if (err != STATUS_OK)
{
return nerr_pass_ctx(err, "In file %s:%d", path, *lineno);
}
}
else if (s[0] == ':') /* link */
{
*s = '\0';
name = neos_strip(name);
s++;
value = neos_strip(s);
err = _set_value (hdf, name, value, 1, 1, 1, attr, NULL);
if (err != STATUS_OK)
{
return nerr_pass_ctx(err, "In file %s:%d", path, *lineno);
}
}
else if (s[0] == '{') /* deeper */
{
*s = '\0';
name = neos_strip(name);
lower = hdf_get_obj (hdf, name);
if (lower == NULL)
{
err = _set_value (hdf, name, NULL, 1, 1, 0, attr, &lower);
}
else
{
err = _set_value (lower, NULL, lower->value, 1, 1, 0, attr, NULL);
}
if (err != STATUS_OK)
{
return nerr_pass_ctx(err, "In file %s:%d", path, *lineno);
}
err = _hdf_read_string (lower, str, line, path, lineno, include_handle);
if (err != STATUS_OK)
{
return nerr_pass_ctx(err, "In file %s:%d", path, *lineno);
}
}
else if (s[0] == '<' && s[1] == '<') /* multi-line assignment */
{
char *m;
int msize = 0;
int mmax = 128;
int l;
*s = '\0';
name = neos_strip(name);
s+=2;
value = neos_strip(s);
l = strlen(value);
if (l == 0)
{
err = nerr_raise(NERR_PARSE,
"[%s:%d] No multi-assignment terminator given: %s", path, *lineno,
line->buf);
return err;
}
m = (char *) malloc (mmax * sizeof(char));
if (m == NULL)
{
return nerr_raise(NERR_NOMEM,
"[%s:%d] Unable to allocate memory for multi-line assignment to %s",
path, *lineno, name);
}
while (_copy_line (str, m+msize, mmax-msize) != 0)
{
(*lineno)++;
if (!strncmp(value, m+msize, l) && isspace(m[msize+l]))
{
m[msize] = '\0';
break;
}
msize += strlen(m+msize);
if (msize + l + 10 > mmax)
{
mmax += 128;
m = (char *) realloc (m, mmax * sizeof(char));
if (m == NULL)
{
return nerr_raise(NERR_NOMEM,
"[%s:%d] Unable to allocate memory for multi-line assignment to %s: size=%d",
path, *lineno, name, mmax);
}
}
}
err = _set_value (hdf, name, m, 0, 1, 0, attr, NULL);
if (err != STATUS_OK)
{
free (m);
return nerr_pass_ctx(err, "In file %s:%d", path, *lineno);
}
}
else
{
err = nerr_raise(NERR_PARSE, "[%s:%d] Unable to parse line %s", path,
*lineno, line->buf);
return err;
}
}
}
return STATUS_OK;
}
NEOERR * hdf_read_string (HDF *hdf, const char *str)
{
NEOERR *err;
int lineno = 0;
STRING line;
string_init(&line);
err = _hdf_read_string(hdf, &str, &line, "<string>", &lineno, INCLUDE_ERROR);
string_clear(&line);
return nerr_pass(err);
}
NEOERR * hdf_read_string_ignore (HDF *hdf, const char *str, int ignore)
{
NEOERR *err;
int lineno = 0;
STRING line;
string_init(&line);
err = _hdf_read_string(hdf, &str, &line, "<string>", &lineno,
(ignore ? INCLUDE_IGNORE : INCLUDE_ERROR));
string_clear(&line);
return nerr_pass(err);
}
/* The search path is part of the HDF by convention */
NEOERR* hdf_search_path (HDF *hdf, const char *path, char *full)
{
HDF *paths;
struct stat s;
for (paths = hdf_get_child (hdf, "hdf.loadpaths");
paths;
paths = hdf_obj_next (paths))
{
snprintf (full, _POSIX_PATH_MAX, "%s/%s", hdf_obj_value(paths), path);
errno = 0;
if (stat (full, &s) == -1)
{
if (errno != ENOENT)
return nerr_raise_errno (NERR_SYSTEM, "Stat of %s failed", full);
}
else
{
return STATUS_OK;
}
}
strncpy (full, path, _POSIX_PATH_MAX);
if (stat (full, &s) == -1)
{
if (errno != ENOENT)
return nerr_raise_errno (NERR_SYSTEM, "Stat of %s failed", full);
}
else return STATUS_OK;
return nerr_raise (NERR_NOT_FOUND, "Path %s not found", path);
}
NEOERR* hdf_read_file (HDF *hdf, const char *path)
{
NEOERR *err;
int lineno = 0;
char fpath[_POSIX_PATH_MAX];
char *ibuf = NULL;
const char *ptr = NULL;
HDF *top = hdf->top;
STRING line;
string_init(&line);
if (path == NULL)
return nerr_raise(NERR_ASSERT, "Can't read NULL file");
if (top->fileload)
{
err = top->fileload(top->fileload_ctx, hdf, path, &ibuf);
}
else
{
if (path[0] != '/')
{
err = hdf_search_path (hdf, path, fpath);
if (err != STATUS_OK) return nerr_pass(err);
path = fpath;
}
err = ne_load_file (path, &ibuf);
}
if (err) return nerr_pass(err);
ptr = ibuf;
err = _hdf_read_string(hdf, &ptr, &line, path, &lineno, INCLUDE_FILE);
free(ibuf);
string_clear(&line);
return nerr_pass(err);
}
void hdf_register_fileload(HDF *hdf, void *ctx, HDFFILELOAD fileload)
{
if (hdf == NULL) return;
if (hdf->top != NULL) hdf = hdf->top;
hdf->fileload_ctx = ctx;
hdf->fileload = fileload;
}