/* 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 . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#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;
}