* UCW Library -- Shell Interface to Configuration Files
*
* (c) 2002--2005 Martin Mares <mj@ucw.cz>
+ * (c) 2006 Robert Spalek <robert@ucw.cz>
+ * (c) 2006 Pavel Charvat <pchar@ucw.cz>
*
* Once we were using this beautiful Shell version, but it turned out
* that it doesn't work with nested config files:
#include "lib/lib.h"
#include "lib/conf.h"
+#include "lib/getopt.h"
+#include "lib/conf-internal.h"
+#include "lib/clists.h"
+#include "lib/mempool.h"
+#include "lib/chartype.h"
+#include "lib/bbuf.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <alloca.h>
-static struct cfitem *items;
-static byte *flags;
-
-enum flag {
- F_STRING = 0,
- F_INT = 1,
- F_DOUBLE = 2,
- F_INT64 = 3,
- F_TYPE_MASK = 7,
- F_ARRAY = 0x80,
-};
-
static void
help(void)
{
fputs("\n\
-Usage: config [-C<configfile>] [-S<section>.<option>=<value>] <section> [@]<item><type>[=<default>] <item2> ... [*]\n\
+Usage: config [-C<configfile>] [-S<section>.<option>=<value>] <sections>\n\
+\n\
+<sections>\t<section>[;<sections>]\n\
+<section>\t[*]<name>{[<items>]}\n\
+<items>\t\t[-]<item>[;<items>]\n\
+<item>\t\t<static> | <array> | <list>\n\
+<static>\t<type><name>[=<value>]\n\
+<list>\t\t@<name>{[<items>]}\n\
+<array>\t\t<type><name><left-bracket>[<number>]<right-bracket>\n\
+<value>\t\t[a-zA-Z0-9.-/]* | 'string without single quotes'<value> | \"c-like string\"<value>\n\
\n\
Types:\n\
<empty>\t\tString\n\
$\t\tFloating point number\n\
\n\
Modifiers:\n\
-@\t\tMultiple occurences of the item are passed as an array (otherwise the last one wins)\n\
-*\t\tIgnore unknown items instead of reporting them as errors\n\
+!\t\tReport unknown items as errors\n\
+-\t\tDo not dump item's value\n\
", stderr);
exit(1);
}
-static byte *
-report(struct cfitem *item, byte *value)
-{
- uns f = flags[item-items];
- byte *err;
- byte buf[128];
+union value {
+ void *v_ptr;
+ int v_int;
+ u64 v_u64;
+ double v_double;
+ clist list;
+};
- if (f & F_ARRAY)
- printf("CF_%s[${#CF_%s[*]}]='", item->name, item->name);
- else
- printf("CF_%s='", item->name);
+#define FLAG_HIDE 0x1
+#define FLAG_NO_UNKNOWN 0x2
- switch (f & F_TYPE_MASK)
- {
- case F_STRING:
- break;
- case F_INT: ;
- uns val;
- if (err = cf_parse_int(value, &val))
- return err;
- sprintf(buf, "%d", val);
- value = buf;
- break;
- case F_INT64: ;
- u64 val64;
- if (err = cf_parse_u64(value, &val64))
- return err;
- sprintf(buf, "%Lu", val64);
- value = buf;
- break;
- case F_DOUBLE: ;
- double valf;
- if (err = cf_parse_double(value, &valf))
- return err;
- sprintf(buf, "%g", valf);
- value = buf;
- break;
- default:
- ASSERT(0);
- }
- while (*value)
- {
- if (*value == '\'')
- die("Apostrophes are not supported in config of scripts");
- putchar(*value++);
- }
- puts("'");
- return NULL;
+struct item {
+ cnode node;
+ uns flags;
+ struct cf_item cf;
+ union value value;
+ uns index;
+};
+
+struct section {
+ struct item item;
+ clist list;
+ uns count;
+ uns size;
+};
+
+static struct mempool *pool;
+static clist sections;
+static byte *pos;
+
+static void
+parse_white(void)
+{
+ while (Cspace(*pos))
+ pos++;
}
-int main(int argc, char **argv)
+static void
+parse_char(byte c)
+{
+ if (*pos++ != c)
+ die("Missing '%c'", c);
+}
+
+static byte *
+parse_name(void)
{
- int i = 1;
- int start;
- struct cfitem *c;
+ byte *name = pos;
+ while (Cword(*pos))
+ pos++;
+ uns len = pos - name;
+ if (!len)
+ die("Expected item/section name");
+ byte *buf = mp_alloc(pool, len + 1);
+ memcpy(buf, name, len);
+ buf[len] = 0;
+ return buf;
+}
- log_init("config");
- while (i < argc && argv[i][0] == '-')
- i++;
- if (i + 1 >= argc)
- help();
- start = i;
- c = items = alloca(sizeof(struct cfitem) * (argc-i+1));
- flags = alloca(argc);
- bzero(flags, argc);
- c->name = argv[i];
- c->type = CT_SECTION;
- c->var = NULL;
- c++;
- while (++i < argc)
+static void
+parse_section(struct section *section)
+{
+#define TRY(x) do{byte *_err=(x); if (_err) die(_err); }while(0)
+ for (uns sep = 0; ; sep = 1)
{
- char *arg = xstrdup(argv[i]);
- if (!strcmp(arg, "*"))
- items->type = CT_INCOMPLETE_SECTION;
+ parse_white();
+ if (!*pos || *pos == '}')
+ break;
+ if (sep)
+ parse_char(';');
+ parse_white();
+
+ struct item *item;
+
+ if (*pos == '@')
+ {
+ pos++;
+ struct section *sec = mp_alloc_zero(pool, sizeof(*sec));
+ sec->size = sizeof(cnode);
+ clist_init(&sec->list);
+ item = &sec->item;
+ item->cf.name = parse_name();
+ item->cf.cls = CC_LIST;
+ item->cf.number = 1;
+ parse_white();
+ parse_char('{');
+ parse_section(sec);
+ parse_char('}');
+ }
else
- {
- uns id = c-items;
- char *e = strchr(arg, '=');
- if (e)
- *e++ = 0;
-
- char *t = arg + strlen(arg) - 1;
- if (t > arg)
+ {
+ item = mp_alloc_zero(pool, sizeof(*item));
+ if (*pos == '-')
{
- if (*t == '#')
- {
- *t-- = 0;
- if (t > arg && *t == '#')
+ item->flags |= FLAG_HIDE;
+ pos++;
+ }
+ item->cf.cls = CC_STATIC;
+ item->cf.number = 1;
+ switch (*pos)
+ {
+ case '#':
+ if (*++pos == '#')
+ {
+ pos++;
+ item->cf.type = CT_U64;
+ }
+ else
+ item->cf.type = CT_INT;
+ break;
+ case '$':
+ pos++;
+ item->cf.type = CT_DOUBLE;
+ break;
+ default:
+ if (!Cword(*pos))
+ die("Invalid type syntax");
+ item->cf.type = CT_STRING;
+ break;
+ }
+ parse_white();
+ item->cf.name = parse_name();
+ parse_white();
+ if (*pos == '[')
+ {
+ pos++;
+ parse_white();
+ item->cf.cls = CC_DYNAMIC;
+ byte *num = pos;
+ while (*pos && *pos != ']')
+ pos++;
+ if (!*pos)
+ die("Missing ']'");
+ *pos++ = 0;
+ if (!*num)
+ item->cf.number = CF_ANY_NUM;
+ else
+ {
+ int inum;
+ TRY(cf_parse_int(num, &inum));
+ if (!inum)
+ die("Invalid array length");
+ item->cf.number = inum;
+ }
+ parse_white();
+ }
+ if (*pos == '=')
+ {
+ pos++;
+ parse_white();
+ if (section->item.cf.cls == CC_LIST)
+ die("List items can not have default values");
+ if (item->cf.cls == CC_DYNAMIC)
+ die("Arrays can not have default values");
+ byte *def = pos, *d = def;
+ while (*pos != ';' && *pos != '}' && !Cspace(*pos))
+ {
+ if (*pos == '\'')
+ {
+ pos++;
+ while (*pos != '\'')
+ {
+ if (!*pos)
+ die("Unterminated string");
+ *d++ = *pos++;
+ }
+ pos++;
+ }
+ else if (*pos == '"')
{
- *t-- = 0;
- flags[id] |= F_INT64;
+ pos++;
+ byte *start = d;
+ uns esc = 0;
+ while (*pos != '"' || esc)
+ {
+ if (!*pos)
+ die("Unterminated string");
+ if (*pos == '\\')
+ esc ^= 1;
+ else
+ esc = 0;
+ *d++ = *pos++;
+ }
+ pos++;
+ *d = 0;
+ d = str_unesc(start, start);
}
else
- flags[id] |= F_INT;
+ *d++ = *pos++;
}
- else if (*t == '$')
- {
- *t-- = 0;
- flags[id] |= F_DOUBLE;
+ uns len = d - def;
+ byte *buf = mp_alloc(pool, len + 1);
+ memcpy(buf, def, len);
+ buf[len] = 0;
+ switch (item->cf.type)
+ {
+ case CT_STRING:
+ item->value.v_ptr = buf;
+ break;
+ case CT_INT:
+ TRY(cf_parse_int(buf, &item->value.v_int));
+ break;
+ case CT_U64:
+ TRY(cf_parse_u64(buf, &item->value.v_u64));
+ break;
+ case CT_DOUBLE:
+ TRY(cf_parse_double(buf, &item->value.v_double));
+ break;
+ default:
+ ASSERT(0);
}
}
+ }
+ if (section->item.cf.cls == CC_LIST)
+ {
+ item->cf.ptr = (void *)(uintptr_t)section->size;
+ section->size += sizeof(union value);
+ }
+ else
+ item->cf.ptr = &item->value;
+ clist_add_tail(§ion->list, &item->node);
+ section->count++;
+ }
+#undef TRY
+}
- if (*arg == '@')
- {
- arg++;
- printf("declare -a CF_%s ; CF_%s=()\n", arg, arg);
- flags[id] |= F_ARRAY;
- }
+static void
+parse_outer(void)
+{
+ for (uns sep = 0; ; sep = 1)
+ {
+ parse_white();
+ if (!*pos)
+ break;
+ if (sep)
+ parse_char(';');
+ parse_white();
+ struct section *sec = mp_alloc_zero(pool, sizeof(*sec));
+ if (*pos == '!')
+ {
+ pos++;
+ sec->item.flags |= FLAG_NO_UNKNOWN;
+ }
+ sec->item.cf.name = parse_name();
+ parse_white();
+ parse_char('{');
+ clist_add_tail(§ions, &sec->item.node);
+ clist_init(&sec->list);
+ parse_section(sec);
+ parse_char('}');
+ }
+}
+
+static struct cf_section *
+generate_section(struct section *section)
+{
+ struct cf_section *sec = mp_alloc_zero(pool, sizeof(*sec));
+ if (section->item.cf.cls == CC_LIST)
+ sec->size = section->size;
+ struct cf_item *c = sec->cfg = mp_alloc_zero(pool, sizeof(struct cf_item) * (section->count + 1));
+ CLIST_FOR_EACH(struct item *, item, section->list)
+ {
+ *c = item->cf;
+ if (c->cls == CC_LIST)
+ c->u.sec = generate_section((struct section *)item);
+ c++;
+ }
+ c->cls = CC_END;
+ return sec;
+}
+
+static bb_t path;
- c->type = CT_FUNCTION;
- c->var = report;
- c->name = arg;
- if (e)
- report(c, e);
- c++;
+static void
+dump_value(uns array, struct item *item, void *v)
+{
+ byte buf[128], *value = buf;
+ if (!array)
+ printf("CF_%s_%s='", path.ptr, item->cf.name);
+ else
+ printf("CF_%s_%s[%u]='", path.ptr, item->cf.name, ++item->index);
+ switch (item->cf.type)
+ {
+ case CT_INT:
+ sprintf(buf, "%d", *(int *)v);
+ break;
+ case CT_U64:
+ sprintf(buf, "%llu", (long long) *(u64 *)v);
+ break;
+ case CT_DOUBLE:
+ sprintf(buf, "%g", *(double *)v);
+ break;
+ case CT_STRING:
+ if (*(byte **)v)
+ value = *(byte **)v;
+ else
+ *value = 0;
+ break;
+ default:
+ ASSERT(0);
+ }
+ while (*value) {
+ if (*value == '\'')
+ printf("'\\''");
+ else
+ putchar(*value);
+ value++;
+ }
+ printf("'\n");
+}
+
+static void
+dump_item(struct item *item, void *ptr, uns path_len)
+{
+ if (item->flags & FLAG_HIDE)
+ return;
+ byte *val = (byte *)((uintptr_t)ptr + (uintptr_t)item->cf.ptr);
+ if (item->cf.cls == CC_LIST)
+ {
+ uns len = strlen(item->cf.name);
+ bb_grow(&path, path_len + len + 1);
+ path.ptr[path_len] = '_';
+ memcpy(path.ptr + path_len + 1, item->cf.name, len);
+ CLIST_FOR_EACH(cnode *, ptr2, *(clist *)val)
+ CLIST_FOR_EACH(struct item *, item2, ((struct section *)item)->list)
+ dump_item(item2, ptr2, path_len + len + 1);
+ }
+ else
+ {
+ bb_grow(&path, path_len + 1)[path_len] = 0;
+ if (item->cf.cls == CC_STATIC)
+ dump_value(!!ptr, item, val);
+ else
+ {
+ val = *(void **)val;
+ uns len = DARY_LEN(val);
+ uns size = cf_type_size(item->cf.type, NULL);
+ for (uns i = 0; i < len; i++, val += size)
+ dump_value(1, item, val);
}
}
- c->type = CT_STOP;
- cf_register(items);
- if (cf_getopt(start, argv, CF_SHORT_OPTS, CF_NO_LONG_OPTS, NULL) != -1)
+}
+
+int main(int argc, char **argv)
+{
+ log_init("config");
+ if (argc < 2)
+ help();
+ pos = argv[argc - 1];
+ argv[argc - 1] = NULL;
+
+ pool = mp_new(0x1000);
+ clist_init(§ions);
+ parse_outer();
+ CLIST_FOR_EACH(struct section *, sec, sections)
+ cf_declare_section(sec->item.cf.name, generate_section(sec), !(sec->item.flags & FLAG_NO_UNKNOWN));
+
+ if (cf_getopt(argc - 1, argv, CF_SHORT_OPTS, CF_NO_LONG_OPTS, NULL) != -1)
help();
+
+ bb_init(&path);
+ CLIST_FOR_EACH(struct section *, section, sections)
+ {
+ uns len = strlen(section->item.cf.name);
+ memcpy(bb_grow(&path, len), section->item.cf.name, len);
+ CLIST_FOR_EACH(struct item *, item, section->list)
+ dump_item(item, NULL, len);
+ }
+ bb_done(&path);
+
return 0;
}
+