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