summaryrefslogtreecommitdiffabout
path: root/src/tree.c
Side-by-side diff
Diffstat (limited to 'src/tree.c') (more/less context) (ignore whitespace changes)
-rw-r--r--src/tree.c773
1 files changed, 773 insertions, 0 deletions
diff --git a/src/tree.c b/src/tree.c
new file mode 100644
index 0000000..52d38c0
--- a/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;
+}

Return to:

Send suggestions and report system problems to the System administrator.