]> mj.ucw.cz Git - libucw.git/blob - lib/shell/config.c
Merge with git+ssh://cvs.ucw.cz/projects/sherlock/GIT/sherlock.git
[libucw.git] / lib / shell / config.c
1 /*
2  *      UCW Library -- Shell Interface to Configuration Files
3  *
4  *      (c) 2002--2005 Martin Mares <mj@ucw.cz>
5  *      (c) 2006 Robert Spalek <robert@ucw.cz>
6  *      (c) 2006 Pavel Charvat <pchar@ucw.cz>
7  *
8  *      Once we were using this beautiful Shell version, but it turned out
9  *      that it doesn't work with nested config files:
10  *
11  *              eval `sed <cf/sherlock '/^#/d;/^ *$/d;s/ \+$//;
12  *              h;s@[^  ]*@@;x;s@[      ].*@@;y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/;G;s/\n//;
13  *              /^\[SECTION\]/,/^\[/ {; /^[A-Z]/ { s/^\([^      ]\+\)[  ]*\(.*\)$/SH_\1="\2"/; p; }; };
14  *              d;'`
15  *
16  *      This software may be freely distributed and used according to the terms
17  *      of the GNU Lesser General Public License.
18  */
19
20 #include "lib/lib.h"
21 #include "lib/conf.h"
22 #include "lib/getopt.h"
23 #include "lib/conf-internal.h"
24 #include "lib/clists.h"
25 #include "lib/mempool.h"
26 #include "lib/chartype.h"
27 #include "lib/bbuf.h"
28
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <alloca.h>
33
34 static void
35 help(void)
36 {
37   fputs("\n\
38 Usage: config [-C<configfile>] [-S<section>.<option>=<value>] <sections>\n\
39 \n\
40 <sections>\t<section>[;<sections>]\n\
41 <section>\t[*]<name>{[<items>]}\n\
42 <items>\t\t[-]<item>[;<items>]\n\
43 <item>\t\t<static> | <array> | <list>\n\
44 <static>\t<type><name>[=<value>]\n\
45 <list>\t\t@<name>{[<items>]}\n\
46 <array>\t\t<type><name><left-bracket>[<number>]<right-bracket>\n\
47 <value>\t\t[a-zA-Z0-9.-/]* | 'string without single quotes'<value> | \"c-like string\"<value>\n\
48 \n\
49 Types:\n\
50 <empty>\t\tString\n\
51 #\t\t32-bit integer\n\
52 ##\t\t64-bit integer\n\
53 $\t\tFloating point number\n\
54 \n\
55 Modifiers:\n\
56 !\t\tReport unknown items as errors\n\
57 -\t\tDo not dump item's value\n\
58 ", stderr);
59   exit(1);
60 }
61
62 union value {
63   void *v_ptr;
64   int v_int;
65   u64 v_u64;
66   double v_double;
67   clist list;
68 };
69
70 #define FLAG_HIDE               0x1
71 #define FLAG_NO_UNKNOWN         0x2
72
73 struct item {
74   cnode node;
75   uns flags;
76   struct cf_item cf;
77   union value value;
78   uns index;
79 };
80
81 struct section {
82   struct item item;
83   clist list;
84   uns count;
85   uns size;
86 };
87
88 static struct mempool *pool;
89 static clist sections;
90 static byte *pos;
91
92 static void
93 parse_white(void)
94 {
95   while (Cspace(*pos))
96     pos++;
97 }
98
99 static void
100 parse_char(byte c)
101 {
102   if (*pos++ != c)
103     die("Missing '%c'", c);
104 }
105
106 static byte *
107 parse_name(void)
108 {
109   byte *name = pos;
110   while (Cword(*pos))
111     pos++;
112   uns len = pos - name;
113   if (!len)
114     die("Expected item/section name");
115   byte *buf = mp_alloc(pool, len + 1);
116   memcpy(buf, name, len);
117   buf[len] = 0;
118   return buf;
119 }
120
121 static void
122 parse_section(struct section *section)
123 {
124 #define TRY(x) do{byte *_err=(x); if (_err) die(_err); }while(0)
125   for (uns sep = 0; ; sep = 1)
126     {
127       parse_white();
128       if (!*pos || *pos == '}')
129         break;
130       if (sep)
131         parse_char(';');
132       parse_white();
133
134       struct item *item;
135
136       if (*pos == '@')
137         {
138           pos++;
139           struct section *sec = mp_alloc_zero(pool, sizeof(*sec));
140           sec->size = sizeof(cnode);
141           clist_init(&sec->list);
142           item = &sec->item;
143           item->cf.name = parse_name();
144           item->cf.cls = CC_LIST;
145           item->cf.number = 1;
146           parse_white();
147           parse_char('{');
148           parse_section(sec);
149           parse_char('}');
150         }
151       else
152         {
153           item = mp_alloc_zero(pool, sizeof(*item));
154           if (*pos == '-')
155             {
156               item->flags |= FLAG_HIDE;
157               pos++;
158             }
159           item->cf.cls = CC_STATIC;
160           item->cf.number = 1;
161           switch (*pos)
162             {
163               case '#':
164                 if (*++pos == '#')
165                   {
166                     pos++;
167                     item->cf.type = CT_U64;
168                   }
169                 else
170                   item->cf.type = CT_INT;
171                 break;
172               case '$':
173                 pos++;
174                 item->cf.type = CT_DOUBLE;
175                 break;
176               default:
177                 if (!Cword(*pos))
178                   die("Invalid type syntax");
179                 item->cf.type = CT_STRING;
180                 break;
181             }
182           parse_white();
183           item->cf.name = parse_name();
184           parse_white();
185           if (*pos == '[')
186             {
187               pos++;
188               parse_white();
189               item->cf.cls = CC_DYNAMIC;
190               byte *num = pos;
191               while (*pos && *pos != ']')
192                 pos++;
193               if (!*pos)
194                 die("Missing ']'");
195               *pos++ = 0;
196               if (!*num)
197                 item->cf.number = CF_ANY_NUM;
198               else
199                 {
200                   int inum;
201                   TRY(cf_parse_int(num, &inum));
202                   if (!inum)
203                     die("Invalid array length");
204                   item->cf.number = inum;
205                 }
206               parse_white();
207             }
208           if (*pos == '=')
209             {
210               pos++;
211               parse_white();
212               if (section->item.cf.cls == CC_LIST)
213                 die("List items can not have default values");
214               if (item->cf.cls == CC_DYNAMIC)
215                 die("Arrays can not have default values");
216               byte *def = pos, *d = def;
217               while (*pos != ';' && *pos != '}' && !Cspace(*pos))
218                 {
219                   if (*pos == '\'')
220                     {
221                       pos++;
222                       while (*pos != '\'')
223                         {
224                           if (!*pos)
225                             die("Unterminated string");
226                           *d++ = *pos++;
227                         }
228                       pos++;
229                     }
230                   else if (*pos == '"')
231                     {
232                       pos++;
233                       byte *start = d;
234                       uns esc = 0;
235                       while (*pos != '"' || esc)
236                         {
237                           if (!*pos)
238                             die("Unterminated string");
239                           if (*pos == '\\')
240                             esc ^= 1;
241                           else
242                             esc = 0;
243                           *d++ = *pos++;
244                         }
245                       pos++;
246                       *d = 0;
247                       d = str_unesc(start, start);
248                     }
249                   else
250                     *d++ = *pos++;
251                 }
252               uns len = d - def;
253               byte *buf = mp_alloc(pool, len + 1);
254               memcpy(buf, def, len);
255               buf[len] = 0;
256               switch (item->cf.type)
257                 {
258                   case CT_STRING:
259                     item->value.v_ptr = buf;
260                     break;
261                   case CT_INT:
262                     TRY(cf_parse_int(buf, &item->value.v_int));
263                     break;
264                   case CT_U64:
265                     TRY(cf_parse_u64(buf, &item->value.v_u64));
266                     break;
267                   case CT_DOUBLE:
268                     TRY(cf_parse_double(buf, &item->value.v_double));
269                     break;
270                   default:
271                     ASSERT(0);
272                 }
273             }
274         }
275       if (section->item.cf.cls == CC_LIST)
276         {
277           item->cf.ptr = (void *)(uintptr_t)section->size;
278           section->size += sizeof(union value);
279         }
280       else
281         item->cf.ptr = &item->value;
282       clist_add_tail(&section->list, &item->node);
283       section->count++;
284     }
285 #undef TRY
286 }
287
288 static void
289 parse_outer(void)
290 {
291   for (uns sep = 0; ; sep = 1)
292     {
293       parse_white();
294       if (!*pos)
295         break;
296       if (sep)
297         parse_char(';');
298       parse_white();
299       struct section *sec = mp_alloc_zero(pool, sizeof(*sec));
300       if (*pos == '!')
301         {
302           pos++;
303           sec->item.flags |= FLAG_NO_UNKNOWN;
304         }
305       sec->item.cf.name = parse_name();
306       parse_white();
307       parse_char('{');
308       clist_add_tail(&sections, &sec->item.node);
309       clist_init(&sec->list);
310       parse_section(sec);
311       parse_char('}');
312     }
313 }
314
315 static struct cf_section *
316 generate_section(struct section *section)
317 {
318   struct cf_section *sec = mp_alloc_zero(pool, sizeof(*sec));
319   if (section->item.cf.cls == CC_LIST)
320     sec->size = section->size;
321   struct cf_item *c = sec->cfg = mp_alloc_zero(pool, sizeof(struct cf_item) * (section->count + 1));
322   CLIST_FOR_EACH(struct item *, item, section->list)
323     {
324       *c = item->cf;
325       if (c->cls == CC_LIST)
326         c->u.sec = generate_section((struct section *)item);
327       c++;
328     }
329   c->cls = CC_END;
330   return sec;
331 }
332
333 static bb_t path;
334
335 static void
336 dump_value(uns array, struct item *item, void *v)
337 {
338   byte buf[128], *value = buf;
339   if (!array)
340     printf("CF_%s_%s='", path.ptr, item->cf.name);
341   else
342     printf("CF_%s_%s[%u]='", path.ptr, item->cf.name, ++item->index);
343   switch (item->cf.type)
344     {
345       case CT_INT:
346         sprintf(buf, "%d", *(int *)v);
347         break;
348       case CT_U64:
349         sprintf(buf, "%llu", (long long) *(u64 *)v);
350         break;
351       case CT_DOUBLE:
352         sprintf(buf, "%g", *(double *)v);
353         break;
354       case CT_STRING:
355         if (*(byte **)v)
356           value = *(byte **)v;
357         else
358           *value = 0;
359         break;
360       default:
361         ASSERT(0);
362     }
363   while (*value) {
364     if (*value == '\'')
365       printf("'\\''");
366     else
367       putchar(*value);
368     value++;
369   }
370   printf("'\n");
371 }
372
373 static void
374 dump_item(struct item *item, void *ptr, uns path_len)
375 {
376   if (item->flags & FLAG_HIDE)
377     return;
378   byte *val = (byte *)((uintptr_t)ptr + (uintptr_t)item->cf.ptr);
379   if (item->cf.cls == CC_LIST)
380     {
381       uns len = strlen(item->cf.name);
382       bb_grow(&path, path_len + len + 1);
383       path.ptr[path_len] = '_';
384       memcpy(path.ptr + path_len + 1, item->cf.name, len);
385       CLIST_FOR_EACH(cnode *, ptr2, *(clist *)val)
386         CLIST_FOR_EACH(struct item *, item2, ((struct section *)item)->list)
387           dump_item(item2, ptr2, path_len + len + 1);
388     }
389   else
390     {
391       bb_grow(&path, path_len + 1)[path_len] = 0;
392       if (item->cf.cls == CC_STATIC)
393         dump_value(!!ptr, item, val);
394       else
395         {
396           val = *(void **)val;
397           uns len = DARY_LEN(val);
398           uns size = cf_type_size(item->cf.type, NULL);
399           for (uns i = 0; i < len; i++, val += size)
400             dump_value(1, item, val);
401         }
402     }
403 }
404
405 int main(int argc, char **argv)
406 {
407   log_init("config");
408   if (argc < 2)
409     help();
410   pos = argv[argc - 1];
411   argv[argc - 1] = NULL;
412
413   pool = mp_new(0x1000);
414   clist_init(&sections);
415   parse_outer();
416   CLIST_FOR_EACH(struct section *, sec, sections)
417     cf_declare_section(sec->item.cf.name, generate_section(sec), !(sec->item.flags & FLAG_NO_UNKNOWN));
418
419   if (cf_getopt(argc - 1, argv, CF_SHORT_OPTS, CF_NO_LONG_OPTS, NULL) != -1)
420     help();
421
422   bb_init(&path);
423   CLIST_FOR_EACH(struct section *, section, sections)
424     {
425       uns len = strlen(section->item.cf.name);
426       memcpy(bb_grow(&path, len), section->item.cf.name, len);
427       CLIST_FOR_EACH(struct item *, item, section->list)
428         dump_item(item, NULL, len);
429     }
430   bb_done(&path);
431
432   return 0;
433 }
434