File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / libpdel / structs / structs.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 23:25:53 2012 UTC (12 years, 3 months ago) by misho
Branches: libpdel, MAIN
CVS tags: v0_5_3, HEAD
libpdel


/*
 * Copyright (c) 2001-2002 Packet Design, LLC.
 * All rights reserved.
 * 
 * Subject to the following obligations and disclaimer of warranty,
 * use and redistribution of this software, in source or object code
 * forms, with or without modifications are expressly permitted by
 * Packet Design; provided, however, that:
 * 
 *    (i)  Any and all reproductions of the source or object code
 *         must include the copyright notice above and the following
 *         disclaimer of warranties; and
 *    (ii) No rights are granted, in any manner or form, to use
 *         Packet Design trademarks, including the mark "PACKET DESIGN"
 *         on advertising, endorsements, or otherwise except as such
 *         appears in the above copyright notice or in the software.
 * 
 * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
 * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
 * OR NON-INFRINGEMENT.  PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
 * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
 * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
 * RELIABILITY OR OTHERWISE.  IN NO EVENT SHALL PACKET DESIGN BE
 * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
 * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
 * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
 * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 PACKET DESIGN IS ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Author: Archie Cobbs <archie@freebsd.org>
 */

#include <sys/types.h>

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include "structs/structs.h"
#include "structs/type/int.h"
#include "structs/type/array.h"
#include "structs/type/string.h"
#include "structs/type/struct.h"
#include "structs/type/union.h"
#include "util/typed_mem.h"

/* Special handling for array length as a read-only field */
static const struct structs_type structs_type_array_length = {
	sizeof(u_int),
	"uint",
	STRUCTS_TYPE_PRIMITIVE,
	structs_region_init,
	structs_region_copy,
	structs_region_equal,
	structs_int_ascify,
	structs_notsupp_binify,
	structs_region_encode_netorder,
	structs_notsupp_decode,
	structs_nothing_free,
	{ { (void *)2 }, { (void *)0 } }    /* args for structs_int_ascify */
};

/* Special handling for union field name as a read-only field */
static const struct structs_type structs_type_union_field_name = {
	sizeof(char *),
	"string",
	STRUCTS_TYPE_PRIMITIVE,
	structs_notsupp_init,
	structs_notsupp_copy,
	structs_string_equal,
	structs_string_ascify,
	structs_notsupp_binify,
	structs_notsupp_encode,
	structs_notsupp_decode,
	structs_nothing_free,
	{ { (void *)"union field_name" },    /* args for structs_type_string */
	{ (void *)0 } }
};

/*
 * Initialize an item.
 */
int
structs_init(const struct structs_type *type, const char *name, void *data)
{
	/* Find item */
	if ((type = structs_find(type, name, &data, 0)) == NULL)
		return (-1);

	/* Initialize it */
	return ((*type->init)(type, data));
}

/*
 * Reset an item.
 */
int
structs_reset(const struct structs_type *type, const char *name, void *data)
{
	void *temp;

	/* Find item */
	if ((type = structs_find(type, name, &data, 0)) == NULL)
		return (-1);

	/* Make a temporary new copy */
	if ((temp = MALLOC(TYPED_MEM_TEMP, type->size)) == NULL)
		return (-1);
	memset(temp, 0, type->size);
	if ((*type->init)(type, temp) == -1) {
		FREE(TYPED_MEM_TEMP, temp);
		return (-1);
	}

	/* Replace existing item, freeing it first */
	(*type->uninit)(type, data);
	memcpy(data, temp, type->size);
	FREE(TYPED_MEM_TEMP, temp);
	return (0);
}

/*
 * Free an item, returning it to its initialized state.
 *
 * If "name" is NULL or empty string, the entire structure is free'd.
 */
int
structs_free(const struct structs_type *type, const char *name, void *data)
{
	const int errno_save = errno;

	/* Find item */
	if ((type = structs_find(type, name, &data, 0)) == NULL)
		return (-1);

	/* Free it */
	(*type->uninit)(type, data);
	errno = errno_save;
	return (0);
}

/*
 * Get a copy of an item.
 *
 * If "name" is NULL or empty string, the entire structure is copied.
 *
 * It is assumed that "to" points to a region big enough to hold
 * the copy of the item. "to" will not be free'd first, so it should
 * not already be initialized.
 *
 * Note: "name" is only applied to "from". The structs types of
 * "from.<name>" and "to" must be the same.
 */
int
structs_get(const struct structs_type *type,
	const char *name, const void *from, void *to)
{
	/* Find item */
	if ((type = structs_find(type, name, (void **)&from, 0)) == NULL)
		return (-1);

	/* Copy item */
	return ((*type->copy)(type, from, to));
}

/*
 * Set an item in a structure.
 *
 * If "name" is NULL or empty string, the entire structure is copied.
 *
 * It is assumed that "to" is already initialized.
 *
 * Note: "name" is only applied to "to". The structs types of
 * "from" and "to.<name>" must be the same.
 */
int
structs_set(const struct structs_type *type,
	const void *from, const char *name, void *to)
{
	void *copy;

	/* Find item */
	if ((type = structs_find(type, name, &to, 0)) == NULL)
		return (-1);

	/* Make a new copy of 'from' */
	if ((copy = MALLOC(TYPED_MEM_TEMP, type->size)) == NULL)
		return (-1);
	if ((*type->copy)(type, from, copy) == -1) {
		FREE(TYPED_MEM_TEMP, copy);
		return (-1);
	}

	/* Free overwritten item in 'to' */
	(*type->uninit)(type, to);

	/* Move new item in its place */
	memcpy(to, copy, type->size);

	/* Done */
	FREE(TYPED_MEM_TEMP, copy);
	return (0);
}

/*
 * Get the ASCII form of an item.
 */
char *
structs_get_string(const struct structs_type *type,
	const char *name, const void *data, const char *mtype)
{
	/* Find item */
	if ((type = structs_find(type, name, (void **)&data, 0)) == NULL)
		return (NULL);

	/* Ascify it */
	return ((*type->ascify)(type, mtype, data));
}

/*
 * Set an item's value from a string.
 *
 * The referred to item must be of a type that supports ASCII encoding,
 * and is assumed to be already initialized.
 */
int
structs_set_string(const struct structs_type *type, const char *name,
	const char *ascii, void *data, char *ebuf, size_t emax)
{
	void *temp;
	char dummy[1];

	/* Sanity check */
	if (ascii == NULL)
		ascii = "";
	if (ebuf == NULL) {
		ebuf = dummy;
		emax = sizeof(dummy);
	}

	/* Initialize error buffer */
	if (emax > 0)
		*ebuf = '\0';

	/* Find item */
	if ((type = structs_find(type, name, &data, 1)) == NULL) {
		strlcpy(ebuf, strerror(errno), emax);
		return (-1);
	}

	/* Binify item into temporary storage */
	if ((temp = MALLOC(TYPED_MEM_TEMP, type->size)) == NULL)
		return (-1);
	memset(temp, 0, type->size);
	if ((*type->binify)(type, ascii, temp, ebuf, emax) == -1) {
		FREE(TYPED_MEM_TEMP, temp);
		if (emax > 0 && *ebuf == '\0')
			strlcpy(ebuf, strerror(errno), emax);
		return (-1);
	}

	/* Replace existing item, freeing it first */
	(*type->uninit)(type, data);
	memcpy(data, temp, type->size);
	FREE(TYPED_MEM_TEMP, temp);
	return (0);
}

/*
 * Get the binary encoded form of an item, allocated with type 'mtype'.
 */
int
structs_get_binary(const struct structs_type *type, const char *name,
	const void *data, const char *mtype, struct structs_data *code)
{
	/* Find item */
	if ((type = structs_find(type, name, (void **)&data, 0)) == NULL) {
		memset(code, 0, sizeof(*code));
		return (-1);
	}

	/* Encode it */
	if ((*type->encode)(type, mtype, code, data) == -1) {
		memset(code, 0, sizeof(*code));
		return (-1);
	}

	/* Done */
	return (0);
}

/*
 * Set an item's value from its binary encoded value.
 */
int
structs_set_binary(const struct structs_type *type, const char *name,
	const struct structs_data *code, void *data, char *ebuf, size_t emax)
{
	void *temp;
	int clen;

	/* Initialize error buffer */
	if (emax > 0)
		*ebuf = '\0';

	/* Find item */
	if ((type = structs_find(type, name, (void **)&data, 0)) == NULL) {
		strlcpy(ebuf, strerror(errno), emax);
		return (-1);
	}

	/* Decode item into temporary storage */
	if ((temp = MALLOC(TYPED_MEM_TEMP, type->size)) == NULL)
		return (-1);
	memset(temp, 0, type->size);
	if ((clen = (*type->decode)(type, code->data,
	    code->length, temp, ebuf, emax)) == -1) {
		FREE(TYPED_MEM_TEMP, temp);
		if (emax > 0 && *ebuf == '\0')
			strlcpy(ebuf, strerror(errno), emax);
		return (-1);
	}
	assert(clen <= code->length);

	/* Replace existing item, freeing it first */
	(*type->uninit)(type, data);
	memcpy(data, temp, type->size);
	FREE(TYPED_MEM_TEMP, temp);

	/* Done */
	return (clen);
}

/*
 * Test for equality.
 */
int
structs_equal(const struct structs_type *type,
	const char *name, const void *data1, const void *data2)
{
	/* Find items */
	if (structs_find(type, name, (void **)&data1, 0) == NULL)
		return (-1);
	if ((type = structs_find(type, name, (void **)&data2, 0)) == NULL)
		return (-1);

	/* Compare them */
	return ((*type->equal)(type, data1, data2));
}

/*
 * Find an item in a structure.
 */
const struct structs_type *
structs_find(const struct structs_type *type, const char *name,
	void **datap, int set_union)
{
	void *data = *datap;
	const char *next;

	/* Empty string means stop recursing */
	if (name == NULL || *name == '\0')
		return (type);

	/* Primitive types don't have sub-elements */
	if (type->tclass == STRUCTS_TYPE_PRIMITIVE) {
		errno = ENOENT;
		return (NULL);
	}

	/* Dereference through pointer(s) */
	while (type->tclass == STRUCTS_TYPE_POINTER) {
		type = type->args[0].v;
		data = *((void **)data);
	}

	/* Get next name component */
	if ((next = strchr(name, STRUCTS_SEPARATOR)) != NULL)
		next++;

	/* Find element of aggregate structure update type and data */
	switch (type->tclass) {
	case STRUCTS_TYPE_ARRAY:
	    {
		const struct structs_type *const etype = type->args[0].v;
		const struct structs_array *const ary = data;
		u_long index;
		char *eptr;

		/* Special handling for "length" */
		if (strcmp(name, "length") == 0) {
			type = &structs_type_array_length;
			data = (void *)&ary->length;
			break;
		}

		/* Decode an index */
		index = strtoul(name, &eptr, 10);
		if (!isdigit(*name)
		    || eptr == name
		    || (*eptr != '\0' && *eptr != STRUCTS_SEPARATOR)) {
			errno = ENOENT;
			return (NULL);
		}
		if (index >= ary->length) {
			errno = EDOM;
			return (NULL);
		}
		type = etype;
		data = (char *)ary->elems + (index * etype->size);
		break;
	    }
	case STRUCTS_TYPE_FIXEDARRAY:
	    {
		const struct structs_type *const etype = type->args[0].v;
		const u_int length = type->args[2].i;
		u_long index;
		char *eptr;

		/* Special handling for "length" */
		if (strcmp(name, "length") == 0) {
			type = &structs_type_array_length;
			data = (void *)&type->args[2].i;
			break;
		}

		/* Decode an index */
		index = strtoul(name, &eptr, 10);
		if (!isdigit(*name)
		    || eptr == name
		    || (*eptr != '\0' && *eptr != STRUCTS_SEPARATOR)) {
			errno = ENOENT;
			return (NULL);
		}
		if (index >= length) {
			errno = EDOM;
			return (NULL);
		}
		type = etype;
		data = (char *)data + (index * etype->size);
		break;
	    }
	case STRUCTS_TYPE_STRUCTURE:
	    {
		const struct structs_field *field;

		/* Find the field */
		for (field = type->args[0].v; field->name != NULL; field++) {
			const size_t fnlen = strlen(field->name);

			/* Handle field names with separator in them */
			if (strncmp(name, field->name, fnlen) == 0
			    && (name[fnlen] == '\0'
			      || name[fnlen] == STRUCTS_SEPARATOR)) {
				next = (name[fnlen] != '\0') ?
				    name + fnlen + 1 : NULL;
				break;
			}
		}
		if (field->name == NULL) {
			errno = ENOENT;
			return (NULL);
		}
		type = field->type;
		data = (char *)data + field->offset;
		break;
	    }
	case STRUCTS_TYPE_UNION:
	    {
		const struct structs_ufield *const fields = type->args[0].v;
		const char *const mtype = type->args[1].s;
		struct structs_union *const un = data;
		const size_t oflen = strlen(un->field_name);
		const struct structs_ufield *ofield;
		const struct structs_ufield *field;
		void *new_un;
		void *data2;

		/* Special handling for "field_name" */
		if (strcmp(name, "field_name") == 0) {
			type = &structs_type_union_field_name;
			data = (void *)&un->field_name;
			break;
		}

		/* Find the old field */
		for (ofield = fields; ofield->name != NULL; ofield++) {
			if (strcmp(ofield->name, un->field_name) == 0)
				break;
		}
		if (ofield->name == NULL) {
			assert(0);
			errno = EINVAL;
			return (NULL);
		}

		/*
		 * Check if the union is already set to the desired field,
		 * handling field names with the separator char in them.
		 */
		if (strncmp(name, un->field_name, oflen) == 0
		    && (name[oflen] == '\0'
		      || name[oflen] == STRUCTS_SEPARATOR)) {
			next = (name[oflen] != '\0') ? name + oflen + 1 : NULL;
			field = ofield;
			goto union_done;
		}

		/* Is modifying the union to get the right name acceptable? */
		if (!set_union) {
			errno = ENOENT;
			return (NULL);
		}

		/* Find the new field */
		for (field = fields; field->name != NULL; field++) {
			const size_t fnlen = strlen(field->name);

			/* Handle field names with separator in them */
			if (strncmp(name, field->name, fnlen) == 0
			    && (name[fnlen] == '\0'
			      || name[fnlen] == STRUCTS_SEPARATOR)) {
				next = (name[fnlen] != '\0') ?
				    name + fnlen + 1 : NULL;
				break;
			}
		}
		if (field->name == NULL) {
			errno = ENOENT;
			return (NULL);
		}

		/* Create a new union with the new field type */
		if ((new_un = MALLOC(mtype, field->type->size)) == NULL)
			return (NULL);
		if ((*field->type->init)(field->type, new_un) == -1) {
			FREE(mtype, new_un);
			return (NULL);
		}

		/* See if name would be found with new union instead of old */
		data2 = new_un;
		if (next != NULL
		    && structs_find(field->type, next, &data2, 1) == NULL) {
			(*field->type->uninit)(field->type, new_un);
			FREE(mtype, new_un);
			return (NULL);
		}

		/* Replace existing union with new one having desired type */
		(*ofield->type->uninit)(ofield->type, un->un);
		FREE(mtype, un->un);
		un->un = new_un;
		*((const char **)&un->field_name) = field->name;

	union_done:
		/* Continue recursing */
		type = field->type;
		data = un->un;
		break;
	    }
	default:
		assert(0);
		return (NULL);
	}

	/* Recurse on sub-element */
	if ((type = structs_find(type, next, &data, set_union)) == NULL)
		return (NULL);

	/* Done */
	*datap = data;
	return (type);
}

/*
 * Traverse a structure.
 */

struct structs_trav {
	char		**list;
	u_int		len;
	u_int		alloc;
	const char	*mtype;
};

static int	structs_trav(struct structs_trav *t, const char *name,
			const struct structs_type *type, const void *data);

int
structs_traverse(const struct structs_type *type,
	const void *data, char ***listp, const char *mtype)
{
	struct structs_trav t;

	/* Initialize traversal structure */
	memset(&t, 0, sizeof(t));
	t.mtype = mtype;

	/* Recurse */
	if (structs_trav(&t, "", type, data) == -1) {
		while (t.len > 0)
			FREE(t.mtype, t.list[--t.len]);
		FREE(t.mtype, t.list);
		return (-1);
	}

	/* Return the result */
	*listp = t.list;
	return (t.len);
}

static int
structs_trav(struct structs_trav *t, const char *name,
	const struct structs_type *type, const void *data)
{
	const char *const dot = (*name == '\0' ? "" : ".");
	char *ename;
	int i;

	/* Dereference through pointer(s) */
	while (type->tclass == STRUCTS_TYPE_POINTER) {
		type = type->args[0].v;
		data = *((void **)data);
	}

	switch (type->tclass) {
	case STRUCTS_TYPE_PRIMITIVE:
	    {
		/* Grow list as necessary */
		if (t->len == t->alloc) {
			u_int new_alloc;
			char **new_list;

			new_alloc = (t->alloc + 32) * 2;
			if ((new_list = REALLOC(t->mtype,
			    t->list, new_alloc * sizeof(*t->list))) == NULL)
				return (-1);
			t->list = new_list;
			t->alloc = new_alloc;
		}

		/* Add new name to list */
		if ((t->list[t->len] = STRDUP(t->mtype, name)) == NULL)
			return (-1);
		t->len++;

		/* Done */
		return (0);
	    }

	case STRUCTS_TYPE_ARRAY:
	    {
		const struct structs_type *const etype = type->args[0].v;
		const struct structs_array *const ary = data;

		/* Iterate over array elements */
		for (i = 0; i < ary->length; i++) {
			const void *const edata
			    = (char *)ary->elems + (i * etype->size);

			if (ASPRINTF(t->mtype,
			    &ename, "%s%s%d", name, dot, i) == -1)
				return (-1);
			if (structs_trav(t, ename, etype, edata) == -1) {
				FREE(t->mtype, ename);
				return (-1);
			}
			FREE(t->mtype, ename);
		}

		/* Done */
		return (0);
	    }

	case STRUCTS_TYPE_FIXEDARRAY:
	    {
		const struct structs_type *const etype = type->args[0].v;
		const u_int length = type->args[2].i;

		/* Iterate over array elements */
		for (i = 0; i < length; i++) {
			const void *const edata
			    = (char *)data + (i * etype->size);

			if (ASPRINTF(t->mtype,
			    &ename, "%s%s%d", name, dot, i) == -1)
				return (-1);
			if (structs_trav(t, ename, etype, edata) == -1) {
				FREE(t->mtype, ename);
				return (-1);
			}
			FREE(t->mtype, ename);
		}

		/* Done */
		return (0);
	    }

	case STRUCTS_TYPE_STRUCTURE:
	    {
		const struct structs_field *field;

		/* Iterate over structure fields */
		for (field = type->args[0].v; field->name != NULL; field++) {
			const void *const edata
			    = (char *)data + field->offset;

			if (ASPRINTF(t->mtype,
			    &ename, "%s%s%s", name, dot, field->name) == -1)
				return (-1);
			if (structs_trav(t, ename, field->type, edata) == -1) {
				FREE(t->mtype, ename);
				return (-1);
			}
			FREE(t->mtype, ename);
		}

		/* Done */
		return (0);
	    }

	case STRUCTS_TYPE_UNION:
	    {
		const struct structs_ufield *const fields = type->args[0].v;
		const struct structs_union *const un = data;
		const struct structs_ufield *field;

		/* Find field */
		for (field = fields; field->name != NULL
		    && strcmp(un->field_name, field->name) != 0; field++);
		if (field->name == NULL) {
			assert(0);
			errno = EINVAL;
			return (-1);
		}

		/* Do selected union field */
		if (ASPRINTF(t->mtype,
		    &ename, "%s%s%s", name, dot, field->name) == -1)
			return (-1);
		if (structs_trav(t, ename, field->type, un->un) == -1) {
			FREE(t->mtype, ename);
			return (-1);
		}
		FREE(t->mtype, ename);

		/* Done */
		return (0);
	    }

	default:
		assert(0);
		errno = ECONNABORTED;
		return (-1);
	}
}


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>