diff options
Diffstat (limited to 'src/tree.c')
-rw-r--r-- | src/tree.c | 773 |
1 files changed, 773 insertions, 0 deletions
diff --git a/src/tree.c b/src/tree.c new file mode 100644 index 0000000..52d38c0 --- /dev/null +++ b/src/tree.c @@ -0,0 +1,773 @@ +/* grecs - Gray's Extensible Configuration System + Copyright (C) 2007-2011 Sergey Poznyakoff + + Grecs is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 3 of the License, or (at your + option) any later version. + + Grecs is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with Grecs. If not, see <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <stdlib.h> +#include "grecs.h" + +struct grecs_node * +grecs_node_create(enum grecs_node_type type, grecs_locus_t *loc) +{ + struct grecs_node *np = grecs_zalloc(sizeof(*np)); + np->type = type; + if (loc) + np->locus = *loc; + return np; +} + +void +grecs_node_bind(struct grecs_node *master, struct grecs_node *node, int dn) +{ + struct grecs_node *np; + + if (dn) { + if (!master->down) + master->down = node; + else { + for (np = master->down; np->next; np = np->next) + ; + np->next = node; + } + for (; node; node = node->next) + node->up = master; + } else { + if (!master->next) + master->next = node; + else { + for (np = master->next; np->next; np = np->next) + ; + np->next = node; + } + node->up = master->up; + } +} + + +static enum grecs_tree_recurse_res +_tree_recurse(struct grecs_node *node, grecs_tree_recursor_t recfun, + void *data) +{ + enum grecs_tree_recurse_res res; +#define CKRES() \ + switch (res) { \ + case grecs_tree_recurse_fail: \ + case grecs_tree_recurse_stop: \ + return res; \ + default: \ + break; \ + } + + for (; node; node = node->next) { + if (node->type == grecs_node_stmt) { + res = recfun(grecs_tree_recurse_set, node, data); + CKRES(); + } else { + switch (recfun(grecs_tree_recurse_pre, node, data)) { + case grecs_tree_recurse_ok: + res = _tree_recurse(node->down, recfun, data); + CKRES(); + res = recfun(grecs_tree_recurse_post, node, + data); + CKRES(); + break; + case grecs_tree_recurse_fail: + return grecs_tree_recurse_fail; + case grecs_tree_recurse_stop: + return grecs_tree_recurse_stop; + case grecs_tree_recurse_skip: + break; + } + } + } + return grecs_tree_recurse_ok; +#undef CKRES +} + +int +grecs_tree_recurse(struct grecs_node *node, grecs_tree_recursor_t recfun, + void *data) +{ + switch (_tree_recurse(node, recfun, data)) { + case grecs_tree_recurse_ok: + case grecs_tree_recurse_stop: + return 0; + default: + break; + } + return 1; +} + +void +grecs_node_free(struct grecs_node *node) +{ + /*FIXME: value*/ + free(node); +} + +void +grecs_tree_free(struct grecs_node *node) +{ + while (node) { + struct grecs_node *next = node->next; + if (node->down) + grecs_tree_free(node->down); + grecs_node_free(node); + node = next; + } +} + + + +static int +fake_callback(enum grecs_callback_command cmd, + grecs_locus_t *locus, + void *varptr, + grecs_value_t *value, + void *cb_data) +{ + return 0; +} + +static struct grecs_keyword fake = { + "*", + NULL, + NULL, + grecs_type_void, + NULL, + 0, + fake_callback, + NULL, + &fake +}; + +static struct grecs_keyword * +find_keyword(struct grecs_keyword *cursect, const char *ident) +{ + struct grecs_keyword *kwp; + + if (cursect && cursect != &fake) { + for (kwp = cursect->kwd; kwp->ident; kwp++) + if (strcmp(kwp->ident, ident) == 0) + return kwp; + } else { + return &fake; + } + return NULL; +} + +static void * +target_ptr(struct grecs_keyword *kwp, char *base) +{ + if (kwp->varptr) + base = (char*) kwp->varptr + kwp->offset; + else if (base) + base += kwp->offset; + + return base; +} + +static int +string_to_bool(const char *string, int *pval, grecs_locus_t *locus) +{ + if (strcmp(string, "yes") == 0 + || strcmp(string, "true") == 0 + || strcmp(string, "t") == 0 + || strcmp(string, "1") == 0) + *pval = 1; + else if (strcmp(string, "no") == 0 + || strcmp(string, "false") == 0 + || strcmp(string, "nil") == 0 + || strcmp(string, "0") == 0) + *pval = 0; + else { + grecs_error(locus, 0, + _("%s: not a valid boolean value"), + string); + return 1; + } + return 0; +} + +static int +string_to_host(struct in_addr *in, const char *string, grecs_locus_t *locus) +{ + if (inet_aton(string, in) == 0) { + struct hostent *hp; + + hp = gethostbyname(string); + if (hp == NULL) + return 1; + memcpy(in, hp->h_addr, sizeof(struct in_addr)); + } + return 0; +} + +static int +string_to_sockaddr(struct grecs_sockaddr *sp, const char *string, + grecs_locus_t *locus) +{ + if (string[0] == '/') { + struct sockaddr_un s_un; + if (strlen(string) >= sizeof(s_un.sun_path)) { + grecs_error(locus, 0, + _("%s: UNIX socket name too long"), + string); + return 1; + } + s_un.sun_family = AF_UNIX; + strcpy(s_un.sun_path, string); + sp->len = sizeof(s_un); + sp->sa = grecs_malloc(sp->len); + memcpy(sp->sa, &s_un, sp->len); + } else { + char *p = strchr(string, ':'); + size_t len; + struct sockaddr_in sa; + + sa.sin_family = AF_INET; + if (p) + len = p - string; + else + len = strlen(string); + + if (len == 0) + sa.sin_addr.s_addr = INADDR_ANY; + else { + char *host = grecs_malloc(len + 1); + memcpy(host, string, len); + host[len] = 0; + + if (string_to_host(&sa.sin_addr, host, locus)) { + grecs_error(locus, 0, + _("%s: not a valid IP address or hostname"), + host); + free(host); + return 1; + } + free(host); + } + + if (p) { + struct servent *serv; + + p++; + serv = getservbyname(p, "tcp"); + if (serv != NULL) + sa.sin_port = serv->s_port; + else { + unsigned long l; + char *q; + + /* Not in services, maybe a number? */ + l = strtoul(p, &q, 0); + + if (*q || l > USHRT_MAX) { + grecs_error(locus, 0, + _("%s: not a valid port number"), p); + return 1; + } + sa.sin_port = htons(l); + } + } else if (grecs_default_port) + sa.sin_port = grecs_default_port; + else { + grecs_error(locus, 0, _("missing port number")); + return 1; + } + sp->len = sizeof(sa); + sp->sa = grecs_malloc(sp->len); + memcpy(sp->sa, &sa, sp->len); + } + return 0; +} + + +/* The TYPE_* defines come from gnulib's intprops.h */ + +/* True if the arithmetic type T is signed. */ +# define TYPE_SIGNED(t) (! ((t) 0 < (t) -1)) + +/* The maximum and minimum values for the integer type T. These + macros have undefined behavior if T is signed and has padding bits. */ +# define TYPE_MINIMUM(t) \ + ((t) (! TYPE_SIGNED(t) \ + ? (t) 0 \ + : TYPE_SIGNED_MAGNITUDE(t) \ + ? ~ (t) 0 \ + : ~ TYPE_MAXIMUM(t))) +# define TYPE_MAXIMUM(t) \ + ((t) (! TYPE_SIGNED(t) \ + ? (t) -1 \ + : ((((t) 1 << (sizeof(t) * CHAR_BIT - 2)) - 1) * 2 + 1))) +# define TYPE_SIGNED_MAGNITUDE(t) ((t) ~ (t) 0 < (t) -1) + + +#define STRTONUM(s, type, base, res, limit, loc) \ + { \ + type sum = 0; \ + \ + for (; *s; s++) \ + { \ + type x; \ + \ + if ('0' <= *s && *s <= '9') \ + x = sum * base + *s - '0'; \ + else if (base == 16 && 'a' <= *s && *s <= 'f') \ + x = sum * base + *s - 'a'; \ + else if (base == 16 && 'A' <= *s && *s <= 'F') \ + x = sum * base + *s - 'A'; \ + else \ + break; \ + if (x <= sum) \ + { \ + grecs_error(loc, 0, _("numeric overflow")); \ + return 1; \ + } \ + else if (limit && x > limit) \ + { \ + grecs_error(loc, 0, _("value out of allowed range")); \ + return 1; \ + } \ + sum = x; \ + } \ + res = sum; \ + } + +#define STRxTONUM(s, type, res, limit, loc) \ + { \ + int base; \ + if (*s == '0') \ + { \ + s++; \ + if (*s == 0) \ + base = 10; \ + else if (*s == 'x' || *s == 'X') \ + { \ + s++; \ + base = 16; \ + } \ + else \ + base = 8; \ + } else \ + base = 10; \ + STRTONUM(s, type, base, res, limit, loc); \ + } + +#define GETUNUM(str, type, res, loc) \ + { \ + type tmpres; \ + const char *s = str; \ + STRxTONUM(s, type, tmpres, 0, loc); \ + if (*s) \ + { \ + grecs_error(loc, 0, _("not a number (stopped near `%s')"), \ + s); \ + return 1; \ + } \ + res = tmpres; \ + } + +#define GETSNUM(str, type, res, loc) \ + { \ + unsigned type tmpres; \ + const char *s = str; \ + int sign; \ + unsigned type limit; \ + \ + if (*s == '-') \ + { \ + sign = 1; \ + s++; \ + limit = TYPE_MINIMUM(type); \ + limit = - limit; \ + } \ + else \ + { \ + sign = 0; \ + limit = TYPE_MAXIMUM(type); \ + } \ + \ + STRxTONUM(s, unsigned type, tmpres, limit, loc); \ + if (*s) \ + { \ + grecs_error(loc, 0, _("not a number (stopped near `%s')"), s); \ + return 1; \ + } \ + res = sign ? - tmpres : tmpres; \ + } + + +int +grecs_string_convert(void *target, enum grecs_data_type type, + const char *string, grecs_locus_t *locus) +{ + switch (type) { + case grecs_type_void: + abort(); + + case grecs_type_string: + *(const char**)target = string; + break; + + case grecs_type_short: + GETUNUM(string, short, *(short*)target, locus); + break; + + case grecs_type_ushort: + GETUNUM(string, unsigned short, *(unsigned short*)target, locus); + break; + + case grecs_type_bool: + return string_to_bool(string, (int*)target, locus); + + case grecs_type_int: + GETSNUM(string, int, *(int*)target, locus); + break; + + case grecs_type_uint: + GETUNUM(string, unsigned int, *(unsigned int*)target, locus); + break; + + case grecs_type_long: + GETSNUM(string, long, *(long*)target, locus); + break; + + case grecs_type_ulong: + GETUNUM(string, unsigned long, *(unsigned long*)target, locus); + break; + + case grecs_type_size: + GETUNUM(string, size_t, *(size_t*)target, locus); + break; + /*FIXME + case grecs_type_off: + GETSNUM(string, off_t, *(off_t*)target, locus); + break; + */ + case grecs_type_time: + /*FIXME: Use getdate */ + GETUNUM(string, time_t, *(time_t*)target, locus); + break; + + case grecs_type_ipv4: + if (inet_aton(string, (struct in_addr *)target)) { + grecs_error(locus, 0, _("%s: not a valid IP address"), + string); + return 1; + } + break; + + case grecs_type_host: + if (string_to_host((struct in_addr *)target, string, locus)) { + grecs_error(locus, 0, + _("%s: not a valid IP address or hostname"), + string); + return 1; + } + break; + + case grecs_type_sockaddr: + return string_to_sockaddr((struct grecs_sockaddr *)target, string, + locus); + + /* FIXME: */ + case grecs_type_cidr: + grecs_error(locus, 0, + _("INTERNAL ERROR at %s:%d"), __FILE__, __LINE__); + abort(); + + case grecs_type_section: + grecs_error(locus, 0, + _("invalid use of block statement")); + return 1; + } + return 0; +} + +struct grecs_prop +{ + size_t size; + int (*cmp)(const void *, const void *); +}; + +static int +string_cmp(const void *elt1, const void *elt2) +{ + return strcmp((const char *)elt1,(const char *)elt2); +} + +#define __grecs_name_cat__(a,b) a ## b +#define NUMCMP(type) __grecs_name_cat__(type,_cmp) +#define __DECL_NUMCMP(type,ctype) \ + static int \ + NUMCMP(type)(const void *elt1, const void *elt2) \ + { \ + return memcmp(elt1, elt2, sizeof(ctype)); \ + } +#define DECL_NUMCMP(type) __DECL_NUMCMP(type,type) + +DECL_NUMCMP(short) +DECL_NUMCMP(int) +DECL_NUMCMP(long) +DECL_NUMCMP(size_t) +/*FIXME DECL_NUMCMP(off_t)*/ +DECL_NUMCMP(time_t) +__DECL_NUMCMP(in_addr, struct in_addr) +__DECL_NUMCMP(grecs_sockaddr, struct grecs_sockaddr) + +struct grecs_prop grecs_prop_tab[] = { + { 0, NULL }, /* grecs_type_void */ + { sizeof(char*), string_cmp }, /* grecs_type_string */ + { sizeof(short), NUMCMP(short) }, /* grecs_type_short */ + { sizeof(unsigned short), NUMCMP(short) }, /* grecs_type_ushort */ + { sizeof(int), NUMCMP(int) }, /* grecs_type_int */ + { sizeof(unsigned int), NUMCMP(int) }, /* grecs_type_uint */ + { sizeof(long), NUMCMP(long) }, /* grecs_type_long */ + { sizeof(unsigned long), NUMCMP(long) }, /* grecs_type_ulong */ + { sizeof(size_t), NUMCMP(size_t) }, /* grecs_type_size */ +#if 0 + FIXME + { sizeof(off_t), NUMCMP(off_t) }, /* grecs_type_off */ +#endif + { sizeof(time_t), NUMCMP(time_t) }, /* grecs_type_time */ + { sizeof(int), NUMCMP(int) }, /* grecs_type_bool */ + { sizeof(struct in_addr), NUMCMP(in_addr) }, /* grecs_type_ipv4 */ + { 0, NULL }, /* FIXME: grecs_type_cidr */ + { sizeof(struct in_addr), NUMCMP(in_addr) }, /* grecs_type_host */ + { sizeof(struct grecs_sockaddr), NUMCMP(grecs_sockaddr) }, + /* grecs_type_sockaddr */ + { 0, NULL } /* grecs_type_section */ +}; +#define grecs_prop_count \ + (sizeof(grecs_prop_tab) / sizeof(grecs_prop_tab[0])) + +void +grecs_process_ident(struct grecs_keyword *kwp, grecs_value_t *value, + void *base, grecs_locus_t *locus) +{ + void *target; + + if (!kwp) + return; + + target = target_ptr(kwp, (char *) base); + + if (kwp->callback) + kwp->callback(grecs_callback_set_value, + locus, + target, + value, + &kwp->callback_data); + else if (value->type == GRECS_TYPE_ARRAY) { + grecs_error(locus, 0, + _("too many arguments to `%s'; missing semicolon?"), + kwp->ident); + return; + } else if (value->type == GRECS_TYPE_LIST) { + if (GRECS_IS_LIST(kwp->type)) { + struct grecs_list_entry *ep; + enum grecs_data_type type = GRECS_TYPE(kwp->type); + int num = 1; + struct grecs_list *list; + size_t size; + + if (type >= grecs_prop_count + || (size = grecs_prop_tab[type].size) == 0) { + grecs_error(locus, 0, + _("INTERNAL ERROR at %s:%d: " + "unhandled data type %d"), + __FILE__, __LINE__, type); + abort(); + } + + list = grecs_list_create(); + list->cmp = grecs_prop_tab[type].cmp; + + for (ep = value->v.list->head; ep; ep = ep->next) { + const grecs_value_t *vp = ep->data; + + if (vp->type != GRECS_TYPE_STRING) + grecs_error(locus, 0, + _("%s: incompatible data type in list item #%d"), + kwp->ident, num); + else if (type == grecs_type_string) + grecs_list_append(list, + (void*) vp->v.string); + else { + void *ptr = grecs_malloc(size); + if (grecs_string_convert(ptr, + type, + vp->v.string, + locus) == 0) + grecs_list_append(list, ptr); + else + free(ptr); + } + } + *(struct grecs_list**)target = list; + } else { + grecs_error(locus, 0, + _("incompatible data type for `%s'"), + kwp->ident); + return; + } + } else if (GRECS_IS_LIST(kwp->type)) { + struct grecs_list *list; + enum grecs_data_type type = GRECS_TYPE(kwp->type); + size_t size; + void *ptr; + + if (type >= grecs_prop_count + || (size = grecs_prop_tab[type].size) == 0) { + grecs_error(locus, 0, + _("INTERNAL ERROR at %s:%d: unhandled data type %d"), + __FILE__, __LINE__, type); + abort(); + } + + list = _grecs_simple_list_create(1); + list->cmp = grecs_prop_tab[type].cmp; + if (type == grecs_type_string) + grecs_list_append(list, value->v.string); + else { + ptr = grecs_malloc(size); + if (grecs_string_convert(ptr, type, value->v.string, locus)) { + free(ptr); + grecs_list_free(list); + return; + } + grecs_list_append(list, ptr); + } + *(struct grecs_list**)target = list; + } else + grecs_string_convert(target, GRECS_TYPE(kwp->type), + value->v.string, + locus); +} + + +struct nodeproc_closure { + struct grecs_keyword *cursect; + struct grecs_list *sections; +}; +#define CURRENT_BASE(clos) \ + ((char*)((clos)->cursect ? (clos)->cursect->callback_data : NULL)) + +static void +stmt_begin(struct nodeproc_closure *clos, + struct grecs_keyword *kwp, struct grecs_node *node) +{ + void *target; + + grecs_list_push(clos->sections, clos->cursect); + if (kwp) { + target = target_ptr(kwp, CURRENT_BASE(clos)); + clos->cursect = kwp; + if (kwp->callback) { + if (kwp->callback(grecs_callback_section_begin, + &node->locus, + target, + &node->value, + &kwp->callback_data)) + clos->cursect = &fake; + } else + kwp->callback_data = target; + } else + /* install an "ignore-all" section */ + clos->cursect = kwp; +} + +static void +stmt_end(struct nodeproc_closure *clos, struct grecs_node *node) +{ + grecs_callback_fn callback = NULL; + void *dataptr = NULL; + struct grecs_keyword *kwp = clos->cursect; + + if (clos->cursect && clos->cursect->callback) { + callback = clos->cursect->callback; + dataptr = &clos->cursect->callback_data; + } + + clos->cursect = (struct grecs_keyword *)grecs_list_pop(clos->sections); + if (!clos->cursect) + abort(); + if (callback) + callback(grecs_callback_section_end, + &node->locus, + kwp ? target_ptr(kwp, CURRENT_BASE(clos)) : NULL, + NULL, + dataptr); + if (kwp) + kwp->callback_data = NULL; +} + +static enum grecs_tree_recurse_res +nodeproc(enum grecs_tree_recurse_op op, struct grecs_node *node, void *data) +{ + struct nodeproc_closure *clos = data; + struct grecs_keyword *kwp; + + switch (op) { + case grecs_tree_recurse_set: + kwp = find_keyword(clos->cursect, node->ident); + if (!kwp) { + grecs_error(&node->locus, 0, _("Unknown keyword")); + return grecs_tree_recurse_skip; + } + grecs_process_ident(kwp, &node->value, CURRENT_BASE(clos), + &node->locus); + break; + + case grecs_tree_recurse_pre: + kwp = find_keyword(clos->cursect, node->ident); + if (!kwp) { + grecs_error(&node->locus, 0, _("Unknown keyword")); + return grecs_tree_recurse_skip; + } + stmt_begin(clos, kwp, node); + break; + + case grecs_tree_recurse_post: + stmt_end(clos, node); + break; + } + return grecs_tree_recurse_ok; +} + +int +grecs_tree_process(struct grecs_node *node, struct grecs_keyword *kwd) +{ + int rc; + struct nodeproc_closure clos; + struct grecs_keyword config_keywords; + + config_keywords.kwd = kwd; + clos.cursect = &config_keywords; + clos.sections = grecs_list_create(); + rc = grecs_tree_recurse(node, nodeproc, &clos); + grecs_list_free(clos.sections); + return rc; +} |