%{
/* 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
#include
#include
#include
#include
typedef union
{
struct sockaddr s;
struct sockaddr_in s_in;
struct sockaddr_un s_un;
} sockaddr_union_t;
static struct grecs_keyword config_keywords;
static struct grecs_keyword *cursect;
#define CURRENT_BASE ((char*)(cursect ? cursect->callback_data : NULL))
static struct grecs_list *sections;
int grecs_error_count;
int grecs_default_port = 0;
static void *target_ptr (struct grecs_keyword *kwp, char *base);
static void stmt_begin (struct grecs_keyword *kwp, grecs_value_t tag);
static void stmt_end (struct grecs_keyword *kwp);
static struct grecs_keyword *find_keyword (const char *ident);
static void process_ident (struct grecs_keyword *kwp, grecs_value_t *value);
static struct grecs_list *simple_list_create (int dispose);
%}
%union {
char *string;
grecs_value_t value;
struct grecs_list *list;
struct grecs_keyword *kw;
}
%token IDENT STRING QSTRING MSTRING
%type string slist
%type slist0
%type value tag vallist
%type values list vlist
%type ident
%%
input : stmtlist
;
stmtlist: stmt
| stmtlist stmt
;
stmt : simple
| block
;
simple : ident vallist ';'
{
process_ident($1, &$2);
}
;
block : ident tag { stmt_begin($1, $2); } '{' stmtlist '}' opt_sc
{
stmt_end($1);
}
;
ident : IDENT
{
$$ = find_keyword($1);
if (!$$)
grecs_error(&grecs_current_locus, 0, _("Unknown keyword"));
}
;
tag : /* empty */
{
$$.type = GRECS_TYPE_STRING;
$$.v.string = NULL;
}
| value
;
vallist : vlist
{
size_t n;
if ((n = grecs_list_size ($1)) == 1)
{
$$ = *(grecs_value_t *)grecs_list_index ($1, 0);
}
else
{
size_t i;
struct grecs_list_entry *ep;
$$.type = GRECS_TYPE_ARRAY;
$$.v.arg.c = n;
$$.v.arg.v = grecs_malloc (n * sizeof ($$.v.arg.v[0]));
for (i = 0, ep = $1->head; ep; i++, ep = ep->next)
$$.v.arg.v[i] = *(grecs_value_t *)ep->data;
}
grecs_list_free ($1);
}
;
vlist : value
{
$$ = simple_list_create (0);
grecs_list_append ($$, grecs_value_dup (&$1));
}
| vlist value
{
grecs_list_append ($1, grecs_value_dup (&$2));
}
;
value : string
{
$$.type = GRECS_TYPE_STRING;
$$.v.string = $1;
}
| list
{
$$.type = GRECS_TYPE_LIST;
$$.v.list = $1;
}
| MSTRING
{
$$.type = GRECS_TYPE_STRING;
$$.v.string = $1;
}
;
string : STRING
| IDENT
| slist
;
slist : slist0
{
struct grecs_list_entry *ep;
const void *p;
grecs_line_begin ();
for (ep = $1->head; ep; ep = ep->next)
grecs_line_add (ep->data, strlen (ep->data));
$$ = grecs_line_finish ();
grecs_list_free ($1);
}
;
slist0 : QSTRING
{
$$ = simple_list_create (0);
grecs_list_append ($$, $1);
}
| slist0 QSTRING
{
grecs_list_append ($1, $2);
$$ = $1;
}
;
list : '(' ')'
{
$$ = NULL;
}
| '(' values ')'
{
$$ = $2;
}
| '(' values ',' ')'
{
$$ = $2;
}
;
values : value
{
$$ = simple_list_create (0);
grecs_list_append ($$, grecs_value_dup (&$1));
}
| values ',' value
{
grecs_list_append ($1, grecs_value_dup (&$3));
$$ = $1;
}
;
opt_sc : /* empty */
| ';'
;
%%
int
yyerror(char *s)
{
grecs_error (&grecs_current_locus, 0, "%s", s);
return 0;
}
static void
listel_dispose (void *el)
{
free (el);
}
static struct grecs_list *
simple_list_create (int dispose)
{
struct grecs_list *lp = grecs_list_create ();
if (dispose)
lp->free_entry = listel_dispose;
return lp;
}
int
grecs_vasprintf (char **pbuf, size_t *psize, const char *fmt, va_list ap)
{
char *buf = *pbuf;
size_t buflen = *psize;
int rc = 0;
if (!buf)
{
if (buflen == 0)
buflen = 512; /* Initial allocation */
buf = calloc (1, buflen);
if (buf == NULL)
return ENOMEM;
}
for (;;)
{
ssize_t n = vsnprintf (buf, buflen, fmt, ap);
if (n < 0 || n >= buflen || !memchr (buf, '\0', n + 1))
{
char *newbuf;
size_t newlen = buflen * 2;
if (newlen < buflen)
{
rc = ENOMEM;
break;
}
newbuf = realloc (buf, newlen);
if (newbuf == NULL)
{
rc = ENOMEM;
break;
}
buflen = newlen;
buf = newbuf;
}
else
break;
}
if (rc)
{
if (!*pbuf)
{
/* We made first allocation, now free it */
free (buf);
buf = NULL;
buflen = 0;
}
}
*pbuf = buf;
*psize = buflen;
return rc;
}
int
grecs_asprintf (char **pbuf, size_t *psize, const char *fmt, ...)
{
int rc;
va_list ap;
va_start (ap, fmt);
rc = grecs_vasprintf (pbuf, psize, fmt, ap);
va_end (ap);
return rc;
}
void
grecs_warning (grecs_locus_t *locus, int errcode, const char *fmt, ...)
{
va_list ap;
char *buf = NULL;
size_t size = 0;
va_start (ap, fmt);
if (grecs_vasprintf (&buf, &size, fmt, ap))
grecs_alloc_die ();
va_end (ap);
grecs_print_diag (locus, 0, errcode, buf);
free(buf);
}
void
grecs_error (grecs_locus_t *locus, int errcode, const char *fmt, ...)
{
va_list ap;
char *buf = NULL;
size_t size = 0;
va_start (ap, fmt);
if (grecs_vasprintf (&buf, &size, fmt, ap))
grecs_alloc_die ();
va_end (ap);
grecs_print_diag (locus, 1, errcode, buf);
free (buf);
grecs_error_count++;
}
void
grecs_set_keywords (struct grecs_keyword *kwd)
{
config_keywords.kwd = kwd;
}
int
grecs_parse (const char *name)
{
int rc;
if (grecs_lex_begin (name))
return 1;
cursect = &config_keywords;
if (sections)
{
grecs_list_free (sections);
sections = NULL;
}
rc = yyparse ();
grecs_lex_end ();
if (grecs_error_count)
rc = 1;
return rc;
}
void
grecs_gram_trace (int n)
{
yydebug = n;
}
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
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 void
stmt_begin (struct grecs_keyword *kwp, grecs_value_t tag)
{
void *target;
if (!sections)
sections = grecs_list_create ();
grecs_list_push (sections, cursect);
if (kwp)
{
target = target_ptr (kwp, CURRENT_BASE);
cursect = kwp;
if (kwp->callback && kwp->callback (grecs_callback_section_begin,
&grecs_current_locus, /* FIXME */
target,
&tag,
&kwp->callback_data))
cursect = &fake;
}
else
/* install "ignore-all" section */
cursect = kwp;
}
static void
stmt_end (struct grecs_keyword *kwp)
{
grecs_callback_fn callback = NULL;
void *dataptr = NULL;
if (cursect && cursect->callback)
{
callback = cursect->callback;
dataptr = &cursect->callback_data;
}
cursect = (struct grecs_keyword *) grecs_list_pop (sections);
if (!cursect)
abort ();
if (callback)
callback (grecs_callback_section_end,
&grecs_current_locus, /* FIXME */
kwp ? target_ptr (kwp, CURRENT_BASE) : NULL,
NULL,
dataptr);
}
static struct grecs_keyword *
find_keyword (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 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_list_create ();
list->cmp = grecs_prop_tab[type].cmp;
list->free_entry = listel_dispose;
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);
}
static void
process_ident (struct grecs_keyword *kwp, grecs_value_t *value)
{
grecs_process_ident (kwp, value, CURRENT_BASE, &grecs_current_locus);
}