%{ /* gconf - General purpose configuration parser. Copyright (C) 2007, 2008, 2009 Sergey Poznyakoff This program 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. This program 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 this program. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #if ENABLE_NLS # include "gettext.h" # define _(msgid) gettext (msgid) #else # define _(msgid) msgid #endif typedef union { struct sockaddr s; struct sockaddr_in s_in; struct sockaddr_un s_un; } sockaddr_union_t; static struct gconf_keyword config_keywords; static struct gconf_keyword *cursect; static gl_list_t sections; int gconf_error_count; int gconf_default_port = 0; static void *target_ptr(struct gconf_keyword *kwp); static void stmt_begin(struct gconf_keyword *kwp, gconf_value_t tag); static void stmt_end(struct gconf_keyword *kwp); static struct gconf_keyword *find_keyword(const char *ident); static void process_ident(struct gconf_keyword *kwp, gconf_value_t *value); static gl_list_t simple_list_create (bool dispose); %} %union { char *string; gconf_value_t value; gl_list_t list; struct gconf_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 (!$$) gconf_error(&gconf_current_locus, 0, _("Unknown keyword")); } ; tag : /* empty */ { $$.type = GCONF_TYPE_STRING; $$.v.string = NULL; } | value ; vallist : vlist { size_t n; if ((n = gl_list_size ($1)) == 1) { $$ = *(gconf_value_t *)gl_list_get_at ($1, 0); } else { size_t i; $$.type = GCONF_TYPE_ARRAY; $$.v.arg.c = n; $$.v.arg.v = xcalloc (n, sizeof ($$.v.arg.v[0])); for (i = 0; i < n; i++) $$.v.arg.v[i] = *(gconf_value_t *)gl_list_get_at ($1, i); } gl_list_free ($1); } ; vlist : value { $$ = simple_list_create (false); gl_list_add_last ($$, gconf_value_dup (&$1)); } | vlist value { gl_list_add_last ($1, gconf_value_dup (&$2)); } ; value : string { $$.type = GCONF_TYPE_STRING; $$.v.string = $1; } | list { $$.type = GCONF_TYPE_LIST; $$.v.list = $1; } | MSTRING { $$.type = GCONF_TYPE_STRING; $$.v.string = $1; } ; string : STRING | IDENT | slist ; slist : slist0 { const void *p; gl_list_iterator_t itr = gl_list_iterator ($1); gconf_line_begin (); while (gl_list_iterator_next (&itr, &p, NULL)) gconf_line_add (p, strlen (p)); $$ = gconf_line_finish (); gl_list_iterator_free (&itr); gl_list_free ($1); } ; slist0 : QSTRING { $$ = simple_list_create (false); gl_list_add_last ($$, $1); } | slist0 QSTRING { gl_list_add_last ($1, $2); $$ = $1; } ; list : '(' ')' { $$ = NULL; } | '(' values ')' { $$ = $2; } | '(' values ',' ')' { $$ = $2; } ; values : value { $$ = simple_list_create (true); gl_list_add_last ($$, gconf_value_dup (&$1)); } | values ',' value { gl_list_add_last ($1, gconf_value_dup (&$3)); $$ = $1; } ; opt_sc : /* empty */ | ';' ; %% int yyerror(char *s) { gconf_error (&gconf_current_locus, 0, "%s", s); return 0; } static void listel_dispose(const void *el) { free((void*)el); } static gl_list_t simple_list_create (bool dispose) { return gl_list_create_empty(&gl_linked_list_implementation, NULL, NULL, dispose ? listel_dispose : NULL, false); } void gconf_warning(gconf_locus_t *locus, int errcode, const char *fmt, ...) { va_list ap; char *buf = NULL; va_start (ap, fmt); vasprintf (&buf, fmt, ap); va_end (ap); gconf_print_diag (locus, 0, errcode, buf); free(buf); } void gconf_error (gconf_locus_t *locus, int errcode, const char *fmt, ...) { va_list ap; char *buf = NULL; va_start (ap, fmt); vasprintf (&buf, fmt, ap); va_end (ap); gconf_print_diag (locus, 1, errcode, buf); free (buf); gconf_error_count++; } void gconf_set_keywords (struct gconf_keyword *kwd) { config_keywords.kwd = kwd; } int gconf_parse (const char *name) { int rc; if (gconf_lex_begin (name)) return 1; cursect = &config_keywords; if (sections) { gl_list_free (sections); sections = NULL; } rc = yyparse (); gconf_lex_end (); if (gconf_error_count) rc = 1; return rc; } void gconf_gram_trace (int n) { yydebug = n; } static void * target_ptr (struct gconf_keyword *kwp) { char *base; if (kwp->varptr) base = (char*) kwp->varptr + kwp->offset; else if (cursect && cursect->callback_data) base = (char*) cursect->callback_data + kwp->offset; else base = NULL; return base; } static int fake_callback (enum gconf_callback_command cmd, gconf_locus_t *locus, void *varptr, gconf_value_t *value, void *cb_data) { return 0; } static struct gconf_keyword fake = { "*", NULL, NULL, gconf_type_void, NULL, 0, fake_callback, NULL, &fake }; static void stmt_begin (struct gconf_keyword *kwp, gconf_value_t tag) { void *target; if (!sections) sections = simple_list_create (false); gl_list_add_first (sections, cursect); if (kwp) { target = target_ptr (kwp); cursect = kwp; if (kwp->callback && kwp->callback (gconf_callback_section_begin, &gconf_current_locus, /* FIXME */ target, &tag, &kwp->callback_data)) cursect = &fake; } else /* install "ignore-all" section */ cursect = kwp; } static void stmt_end (struct gconf_keyword *kwp) { gconf_callback_fn callback = NULL; void *dataptr = NULL; if (cursect && cursect->callback) { callback = cursect->callback; dataptr = &cursect->callback_data; } if (gl_list_size (sections) == 0) abort (); cursect = (struct gconf_keyword *) gl_list_get_at (sections, 0); gl_list_remove_at (sections, 0); if (callback) callback (gconf_callback_section_end, &gconf_current_locus, /* FIXME */ kwp ? target_ptr(kwp) : NULL, NULL, dataptr); } static struct gconf_keyword * find_keyword (const char *ident) { struct gconf_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_signed (intmax_t *sval, const char *string, intmax_t minval, intmax_t maxval) { intmax_t t; char *p; t = strtoimax (string, &p, 0); if (*p) { gconf_error (&gconf_current_locus, 0, _("cannot convert `%s' to number"), string); return 1; } else if (t < minval || t > maxval) { gconf_error (&gconf_current_locus, 0, _("%s: value out of allowed range %"PRIiMAX"..%"PRIiMAX), string, minval, maxval); return 1; } *sval = t; return 0; } static int string_to_unsigned (uintmax_t *sval, const char *string, uintmax_t maxval, gconf_locus_t *loc) { uintmax_t t; char *p; t = strtoumax (string, &p, 0); if (*p) { gconf_error (loc, 0, _("cannot convert `%s' to number"), string); return 1; } else if (t > maxval) { gconf_error (loc, 0, _("%s: value out of allowed range 0..%"PRIuMAX), string, maxval); return 1; } *sval = t; return 0; } static int string_to_bool (const char *string, int *pval) { 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 { gconf_error (&gconf_current_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) { 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 gconf_sockaddr *sp, const char *string) { if (string[0] == '/') { struct sockaddr_un s_un; if (strlen (string) >= sizeof (s_un.sun_path)) { gconf_error (&gconf_current_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 = xmalloc (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 = xmalloc (len + 1); memcpy (host, string, len); host[len] = 0; if (string_to_host (&sa.sin_addr, host)) { gconf_error (&gconf_current_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) { gconf_error (&gconf_current_locus, 0, _("%s: not a valid port number"), p); return 1; } sa.sin_port = htons (l); } } else if (gconf_default_port) sa.sin_port = gconf_default_port; else { gconf_error (&gconf_current_locus, 0, _("missing port number")); return 1; } sp->len = sizeof (sa); sp->sa = xmalloc (sp->len); memcpy (sp->sa, &sa, sp->len); } return 0; } int gconf_string_convert (void *target, enum gconf_data_type type, const char *string) { uintmax_t uval; intmax_t sval; switch (type) { case gconf_type_void: abort (); case gconf_type_string: *(const char**)target = string; break; case gconf_type_short: if (string_to_signed (&sval, string, SHRT_MIN, SHRT_MAX) == 0) *(short*)target = sval; else return 1; break; case gconf_type_ushort: if (string_to_unsigned (&uval, string, USHRT_MAX, &gconf_current_locus) == 0) *(unsigned short*)target = uval; else return 1; break; case gconf_type_bool: return string_to_bool (string, (int*)target); case gconf_type_int: if (string_to_signed (&sval, string, INT_MIN, INT_MAX) == 0) *(int*)target = sval; else return 1; break; case gconf_type_uint: if (string_to_unsigned (&uval, string, UINT_MAX, &gconf_current_locus) == 0) *(unsigned int*)target = uval; else return 1; break; case gconf_type_long: if (string_to_signed (&sval, string, LONG_MIN, LONG_MAX) == 0) *(long*)target = sval; else return 1; break; case gconf_type_ulong: if (string_to_unsigned (&uval, string, ULONG_MAX, &gconf_current_locus) == 0) *(unsigned long*)target = uval; else return 1; break; case gconf_type_size: if (string_to_unsigned (&uval, string, SIZE_MAX, &gconf_current_locus) == 0) *(size_t*)target = uval; else return 1; break; case gconf_type_intmax: return string_to_signed ((intmax_t*)target, string, INTMAX_MIN, INTMAX_MAX); case gconf_type_uintmax: return string_to_unsigned ((uintmax_t*)target, string, UINTMAX_MAX, &gconf_current_locus); case gconf_type_time: /*FIXME: Use getdate */ if (string_to_unsigned (&uval, string, (time_t)-1, &gconf_current_locus) == 0) *(time_t*)target = uval; else return 1; break; case gconf_type_ipv4: if (inet_aton (string, (struct in_addr *)target)) { gconf_error (&gconf_current_locus, 0, _("%s: not a valid IP address"), string); return 1; } break; case gconf_type_host: if (string_to_host ((struct in_addr *)target, string)) { gconf_error (&gconf_current_locus, 0, _("%s: not a valid IP address or hostname"), string); return 1; } break; case gconf_type_sockaddr: return string_to_sockaddr ((struct gconf_sockaddr *)target, string); /* FIXME: */ case gconf_type_cidr: gconf_error (&gconf_current_locus, 0, _("INTERNAL ERROR at %s:%d"), __FILE__, __LINE__); abort(); case gconf_type_section: gconf_error (&gconf_current_locus, 0, _("Invalid use of block statement")); return 1; } return 0; } struct gconf_prop { size_t size; gl_listelement_equals_fn eqfn; }; static bool string_eq (const void *elt1, const void *elt2) { return strcmp ((const char *)elt1, (const char *)elt2) == 0; } #define __gconf_name_cat__(a,b) a ## b #define NUMEQ(type) __gconf_name_cat__(type,_eq) #define __DECL_NUMEQ(type,ctype) \ static bool \ NUMEQ(type) (const void *elt1, const void *elt2) \ { \ return memcmp (elt1, elt2, sizeof (ctype)) == 0; \ } #define DECL_NUMEQ(type) __DECL_NUMEQ(type,type) DECL_NUMEQ(short) DECL_NUMEQ(int) DECL_NUMEQ(long) DECL_NUMEQ(size_t) DECL_NUMEQ(uintmax_t) DECL_NUMEQ(intmax_t) DECL_NUMEQ(time_t) __DECL_NUMEQ(in_addr, struct in_addr) __DECL_NUMEQ(gconf_sockaddr, struct gconf_sockaddr) struct gconf_prop gconf_prop_tab[] = { { 0, NULL }, /* gconf_type_void */ { sizeof (char*), string_eq }, /* gconf_type_string */ { sizeof (short), NUMEQ (short) }, /* gconf_type_short */ { sizeof (unsigned short), NUMEQ (short) }, /* gconf_type_ushort */ { sizeof (int), NUMEQ (int) }, /* gconf_type_int */ { sizeof (unsigned int), NUMEQ (int) }, /* gconf_type_uint */ { sizeof (long), NUMEQ (long) }, /* gconf_type_long */ { sizeof (unsigned long), NUMEQ (long) }, /* gconf_type_ulong */ { sizeof (size_t), NUMEQ (size_t) }, /* gconf_type_size */ /* gconf_type_off,*/ { sizeof (uintmax_t), NUMEQ (uintmax_t) }, /* gconf_type_uintmax */ { sizeof (intmax_t), NUMEQ (intmax_t) }, /* gconf_type_intmax */ { sizeof (time_t), NUMEQ (time_t) }, /* gconf_type_time */ { sizeof (int), NUMEQ (int) }, /* gconf_type_bool */ { sizeof (struct in_addr), NUMEQ (in_addr) }, /* gconf_type_ipv4 */ { 0, NULL }, /* FIXME: gconf_type_cidr */ { sizeof (struct in_addr), NUMEQ (in_addr) }, /* gconf_type_host */ { sizeof (struct gconf_sockaddr), NUMEQ (gconf_sockaddr) }, /* gconf_type_sockaddr */ { 0, NULL } /* gconf_type_section */ }; #define gconf_prop_count \ (sizeof (gconf_prop_tab) / sizeof (gconf_prop_tab[0])) static void process_ident (struct gconf_keyword *kwp, gconf_value_t *value) { void *target; if (!kwp) return; target = target_ptr (kwp); if (kwp->callback) kwp->callback (gconf_callback_set_value, &gconf_current_locus, /* FIXME */ target, value, &kwp->callback_data); else if (value->type == GCONF_TYPE_ARRAY) { gconf_error (&gconf_current_locus, 0, _("too many arguments to `%s'; missing semicolon?"), kwp->ident); return; } else if (value->type == GCONF_TYPE_LIST) { if (GCONF_IS_LIST (kwp->type)) { gl_list_iterator_t itr = gl_list_iterator (value->v.list); enum gconf_data_type type = GCONF_TYPE (kwp->type); int num = 1; const void *p; gl_list_t list; size_t size; if (type >= gconf_prop_count || (size = gconf_prop_tab[type].size) == 0) { gconf_error (&gconf_current_locus, 0, _("INTERNAL ERROR at %s:%d: " "unhandled data type %d"), __FILE__, __LINE__, type); abort (); } list = gl_list_create_empty (&gl_linked_list_implementation, gconf_prop_tab[type].eqfn, NULL, listel_dispose, false); while (gl_list_iterator_next (&itr, &p, NULL)) { const gconf_value_t *vp = p; if (vp->type != GCONF_TYPE_STRING) gconf_error (&gconf_current_locus, 0, _("%s: incompatible data type in list item #%d"), kwp->ident, num); else if (type == gconf_type_string) gl_list_add_last (list, vp->v.string); else { void *ptr = xmalloc (size); if (gconf_string_convert (ptr, type, vp->v.string) == 0) gl_list_add_last (list, ptr); else free (ptr); } } gl_list_iterator_free (&itr); *(gl_list_t*)target = list; } else { gconf_error (&gconf_current_locus, 0, _("incompatible data type for `%s'"), kwp->ident); return; } } else if (GCONF_IS_LIST (kwp->type)) { gl_list_t list; enum gconf_data_type type = GCONF_TYPE (kwp->type); size_t size; void *ptr; if (type >= gconf_prop_count || (size = gconf_prop_tab[type].size) == 0) { gconf_error (&gconf_current_locus, 0, _("INTERNAL ERROR at %s:%d: unhandled data type %d"), __FILE__, __LINE__, type); abort(); } list = gl_list_create_empty (&gl_linked_list_implementation, gconf_prop_tab[type].eqfn, NULL, listel_dispose, false); if (type == gconf_type_string) gl_list_add_last (list, value->v.string); else { ptr = xmalloc (size); if (gconf_string_convert (ptr, type, value->v.string)) { free (ptr); gl_list_free (list); return; } gl_list_add_last (list, ptr); } *(gl_list_t*)target = list; } else gconf_string_convert (target, GCONF_TYPE (kwp->type), value->v.string); }