]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/ini.c
Update version + date
[bacula/bacula] / bacula / src / lib / ini.c
1 /*
2    Copyright (C) 2011-2011 Bacula Systems(R) SA
3
4    The main author of Bacula is Kern Sibbald, with contributions from
5    many others, a complete list can be found in the file AUTHORS.
6    This program is Free Software; you can modify it under the terms of
7    version three of the GNU Affero General Public License as published by the 
8    Free Software Foundation, which is listed in the file LICENSE.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU Affero General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18    02110-1301, USA.
19
20    Bacula® is a registered trademark of Kern Sibbald.
21    Bacula Systems(R) is a trademark of Bacula Systems SA.
22    Bacula Enterprise(TM) is a trademark of Bacula Systems SA.
23
24    The licensor of Bacula Enterprise(TM) is Bacula Systems(R) SA,
25    Rue Galilee 5, 1400 Yverdon-les-Bains, Switzerland.
26 */
27
28 /*
29  * Handle simple configuration file such as "ini" files.
30  * key1 = val     # comment
31  * key2 = val     # <type>
32  * 
33  */
34
35 #include "bacula.h"
36 #include "ini.h"
37
38 #define bfree_and_null_const(a) do{if(a){free((void *)a); (a)=NULL;}} while(0)
39 static int dbglevel = 100;
40
41 /* We use this structure to associate a key to the function */
42 struct ini_store {
43    const char *key;
44    const char *comment;
45    INI_ITEM_HANDLER *handler;
46 };
47
48 static struct ini_store funcs[] = {
49    {"@INT32@",  "Integer",          ini_store_int32},
50    {"@PINT32@", "Integer",          ini_store_pint32},
51    {"@PINT64@", "Positive Integer", ini_store_pint64},
52    {"@INT64@",  "Integer",          ini_store_int64},
53    {"@NAME@",   "Simple String",    ini_store_name},
54    {"@STR@",    "String",           ini_store_str},
55    {"@BOOL@",   "on/off",           ini_store_bool},
56    {"@ALIST@",  "String list",      ini_store_alist_str},
57    {NULL,       NULL,               NULL}
58 };
59
60 /* 
61  * Get handler code from handler @ 
62  */
63 const char *ini_get_store_code(INI_ITEM_HANDLER *handler)
64 {
65    for (int i = 0; funcs[i].key ; i++) {
66       if (funcs[i].handler == handler) {
67          return funcs[i].key;
68       }
69    }
70    return NULL;
71 }
72
73 /* 
74  * Get handler function from handler name 
75  */
76 INI_ITEM_HANDLER *ini_get_store_handler(const char *key)
77 {
78    for (int i = 0; funcs[i].key ; i++) {
79       if (!strcmp(funcs[i].key, key)) {
80          return funcs[i].handler;
81       }
82    }   
83    return NULL;
84 }
85
86 /*
87  * Format a scanner error message
88  */
89 static void s_err(const char *file, int line, LEX *lc, const char *msg, ...)
90 {
91    ConfigFile *ini = (ConfigFile *)(lc->caller_ctx);
92    va_list arg_ptr;
93    char buf[MAXSTRING];
94
95    va_start(arg_ptr, msg);
96    bvsnprintf(buf, sizeof(buf), msg, arg_ptr);
97    va_end(arg_ptr);
98
99 #ifdef TEST_PROGRAM
100    printf("ERROR: Config file error: %s\n"
101           "            : Line %d, col %d of file %s\n%s\n",
102       buf, lc->line_no, lc->col_no, lc->fname, lc->line);
103 #endif
104
105    if (ini->jcr) {              /* called from core */
106       Jmsg(ini->jcr, M_ERROR, 0, _("Config file error: %s\n"
107                               "            : Line %d, col %d of file %s\n%s\n"),
108          buf, lc->line_no, lc->col_no, lc->fname, lc->line);
109
110 //   } else if (ini->ctx) {       /* called from plugin */
111 //      ini->bfuncs->JobMessage(ini->ctx, __FILE__, __LINE__, M_FATAL, 0, 
112 //                    _("Config file error: %s\n"
113 //                      "            : Line %d, col %d of file %s\n%s\n"),
114 //                            buf, lc->line_no, lc->col_no, lc->fname, lc->line);
115 //
116    } else {                     /* called from ??? */
117       e_msg(file, line, M_ERROR, 0, 
118             _("Config file error: %s\n"
119               "            : Line %d, col %d of file %s\n%s\n"),
120             buf, lc->line_no, lc->col_no, lc->fname, lc->line);
121    }
122 }
123
124 /* Reset free items */
125 void ConfigFile::clear_items()
126 {
127    if (!items) {
128       return;
129    }
130
131    for (int i=0; items[i].name; i++) {
132       if (items[i].found) {
133          /* special members require delete or free */
134          if (items[i].handler == ini_store_str) {
135             free(items[i].val.strval);
136             items[i].val.strval = NULL;
137
138          } else if (items[i].handler == ini_store_alist_str) {
139             delete items[i].val.alistval;
140             items[i].val.alistval = NULL;            
141          }
142          items[i].found = false;
143       }
144    }
145 }
146
147 void ConfigFile::free_items()
148 {
149    if (items_allocated) {
150       for (int i=0; items[i].name; i++) {
151          bfree_and_null_const(items[i].name);
152          bfree_and_null_const(items[i].comment);
153       }
154       free(items);
155    }
156    items = NULL;
157    items_allocated = false;
158 }
159
160 /* Get a particular item from the items list */
161 int ConfigFile::get_item(const char *name)
162 {
163    if (!items) {
164       return -1;
165    }
166
167    for (int i=0; i < MAX_INI_ITEMS && items[i].name; i++) {
168       if (strcasecmp(name, items[i].name) == 0) {
169          return i;
170       }
171    }
172    return -1;
173 }
174
175 /* Dump a buffer to a file in the working directory
176  * Needed to unserialise() a config
177  */
178 bool ConfigFile::dump_string(const char *buf, int32_t len)
179 {
180    FILE *fp;
181    bool ret=false;
182
183    if (!out_fname) {
184       out_fname = get_pool_memory(PM_FNAME);
185       make_unique_filename(&out_fname, (int)(intptr_t)this, (char*)"configfile");
186    }
187    
188    fp = fopen(out_fname, "wb");
189    if (!fp) {
190       return ret;
191    }
192
193    if (fwrite(buf, len, 1, fp) == 1) {
194       ret = true;
195    }
196
197    fclose(fp);
198    return ret;
199 }
200
201 /* Dump the item table format to a text file (used by plugin) */
202 bool ConfigFile::serialize(const char *fname)
203 {
204    FILE *fp;
205    POOLMEM *tmp;
206    int32_t len;
207    bool ret = false;
208
209    if (!items) {
210       return ret;
211    }
212    
213    fp = fopen(fname, "w");
214    if (!fp) {
215       return ret;
216    }
217
218    tmp = get_pool_memory(PM_MESSAGE);
219    len = serialize(&tmp);
220    if (fwrite(tmp, len, 1, fp) == 1) {
221       ret = true;
222    }
223    free_pool_memory(tmp);
224
225    fclose(fp);
226    return ret;
227 }
228
229 /* Dump the item table format to a text file (used by plugin) */
230 int ConfigFile::serialize(POOLMEM **buf)
231 {
232    int len;
233    POOLMEM *tmp;
234    if (!items) {
235       **buf = 0;
236       return 0;
237    }
238    
239    len = Mmsg(buf, "# Plugin configuration file\n# Version %d\n", version);
240
241    tmp = get_pool_memory(PM_MESSAGE);
242
243    for (int i=0; items[i].name ; i++) {
244       if (items[i].comment) {
245          Mmsg(tmp, "OptPrompt=%s\n", items[i].comment);
246          pm_strcat(buf, tmp);
247       }
248       if (items[i].default_value) {
249          Mmsg(tmp, "OptDefault=%s\n", items[i].default_value);
250          pm_strcat(buf, tmp);
251       }
252       if (items[i].required) {
253          Mmsg(tmp, "OptRequired=yes\n");
254          pm_strcat(buf, tmp);
255       }
256
257       /* variable = @INT64@ */
258       Mmsg(tmp, "%s=%s\n\n", 
259            items[i].name, ini_get_store_code(items[i].handler));
260       len = pm_strcat(buf, tmp);
261    }
262    free_pool_memory(tmp);
263
264    return len ;
265 }
266
267 /* Dump the item table content to a text file (used by director) */
268 int ConfigFile::dump_results(POOLMEM **buf)
269 {
270    int len;
271    POOLMEM *tmp;
272    if (!items) {
273       **buf = 0;
274       return 0;
275    }
276    len = Mmsg(buf, "# Plugin configuration file\n# Version %d\n", version);
277
278    tmp = get_pool_memory(PM_MESSAGE);
279
280    for (int i=0; items[i].name ; i++) {
281       if (items[i].found) {
282          items[i].handler(NULL, this, &items[i]);
283          if (items[i].comment && *items[i].comment) {
284             Mmsg(tmp, "# %s\n", items[i].comment);
285             pm_strcat(buf, tmp);
286          }
287          Mmsg(tmp, "%s=%s\n\n", items[i].name, this->edit);
288          len = pm_strcat(buf, tmp);
289       }
290    }
291    free_pool_memory(tmp);
292
293    return len ;
294 }
295
296 /* Parse a config file used by Plugin/Director */
297 bool ConfigFile::parse(const char *fname)
298 {
299    int token, i;
300    bool ret=false;
301
302    if (!items) {
303       return false;
304    }
305
306    if ((lc = lex_open_file(lc, fname, s_err)) == NULL) {
307       berrno be;
308       Emsg2(M_ERROR, 0, _("Cannot open config file %s: %s\n"),
309             fname, be.bstrerror());
310       return false;
311    }
312    lc->options |= LOPT_NO_EXTERN;
313    lc->caller_ctx = (void *)this;
314
315    while ((token=lex_get_token(lc, T_ALL)) != T_EOF) {
316       Dmsg1(dbglevel, "parse got token=%s\n", lex_tok_to_str(token));
317       if (token == T_EOL) {
318          continue;
319       }
320       for (i=0; items[i].name; i++) {
321          if (strcasecmp(items[i].name, lc->str) == 0) {
322             if ((token = lex_get_token(lc, T_EQUALS)) == T_ERROR) {
323                Dmsg1(dbglevel, "in T_IDENT got token=%s\n",
324                      lex_tok_to_str(token));
325                break;
326             }
327
328             Dmsg1(dbglevel, "calling handler for %s\n", items[i].name);
329             /* Call item handler */
330             ret = items[i].found = items[i].handler(lc, this, &items[i]);
331             i = -1;
332             break;
333          }
334       }
335       if (i >= 0) {
336          Dmsg1(dbglevel, "Keyword = %s\n", lc->str);
337          scan_err1(lc, "Keyword %s not found", lc->str);
338          /* We can raise an error here */
339          break;
340       }
341       if (!ret) {
342          break;
343       }
344    }
345
346    for (i=0; items[i].name; i++) {
347       if (items[i].required && !items[i].found) {
348          scan_err1(lc, "%s required but not found", items[i].name);
349          ret = false;
350       }
351    }
352
353    lc = lex_close_file(lc);
354
355    return ret;
356 }
357
358 /* Analyse the content of a ini file to build the item list
359  * It uses special syntax for datatype. Used by Director on Restore object
360  *
361  * OptPrompt = "Variable1"
362  * OptRequired
363  * OptDefault = 100
364  * Variable1 = @PINT32@
365  * ...
366  */
367 bool ConfigFile::unserialize(const char *fname)
368 {
369    int token, i, nb = 0;
370    bool ret=false;
371    const char **assign;
372
373    /* At this time, we allow only 32 different items */
374    int s = MAX_INI_ITEMS * sizeof (struct ini_items);
375
376    items = (struct ini_items *) malloc (s);
377    memset(items, 0, s);
378    items_allocated = true;
379
380    /* parse the file and generate the items structure on the fly */
381    if ((lc = lex_open_file(lc, fname, s_err)) == NULL) {
382       berrno be;
383       Emsg2(M_ERROR, 0, _("Cannot open config file %s: %s\n"),
384             fname, be.bstrerror());
385       return false;
386    }
387    lc->options |= LOPT_NO_EXTERN;
388    lc->caller_ctx = (void *)this;
389
390    while ((token=lex_get_token(lc, T_ALL)) != T_EOF) {
391       Dmsg1(dbglevel, "parse got token=%s\n", lex_tok_to_str(token));
392
393       if (token == T_EOL) {
394          continue;
395       }
396
397       ret = false;
398       assign = NULL;
399
400       if (nb >= MAX_INI_ITEMS) {
401          break;
402       }
403
404       if (strcasecmp("optprompt", lc->str) == 0) {
405          assign = &(items[nb].comment);
406
407       } else if (strcasecmp("optdefault", lc->str) == 0) {
408          assign = &(items[nb].default_value);
409
410       } else if (strcasecmp("optrequired", lc->str) == 0) { 
411          items[nb].required = true;               /* Don't use argument */
412          scan_to_eol(lc);
413          continue;
414
415       } else {
416          items[nb].name = bstrdup(lc->str);
417       }
418
419       token = lex_get_token(lc, T_ALL);
420       Dmsg1(dbglevel, "in T_IDENT got token=%s\n", lex_tok_to_str(token));
421
422       if (token != T_EQUALS) {
423          scan_err1(lc, "expected an equals, got: %s", lc->str);
424          break;
425       }
426
427       /* We may allow blank variable */
428       if (lex_get_token(lc, T_STRING) == T_ERROR) {
429          break;
430       }
431
432       if (assign) {
433          *assign = bstrdup(lc->str);
434
435       } else {
436          if ((items[nb].handler = ini_get_store_handler(lc->str)) == NULL) {
437             scan_err1(lc, "expected a data type, got: %s", lc->str);
438             break;
439          }
440          nb++;
441       }
442       scan_to_eol(lc);
443       ret = true;
444    }
445
446    if (!ret) {
447       for (i = 0; i < nb ; i++) {
448          bfree_and_null_const(items[i].name);
449          bfree_and_null_const(items[i].comment);
450          bfree_and_null_const(items[i].default_value);
451          items[i].handler = NULL;
452          items[i].required = false;
453       }
454    }
455
456    lc = lex_close_file(lc);
457    return ret;
458 }
459
460 /* ----------------------------------------------------------------
461  * Handle data type. Import/Export
462  * ----------------------------------------------------------------
463  */
464 bool ini_store_str(LEX *lc, ConfigFile *inifile, ini_items *item)
465 {
466    if (!lc) {
467       Mmsg(inifile->edit, "%s", item->val.strval);
468       return true;
469    }
470    if (lex_get_token(lc, T_STRING) == T_ERROR) {
471       return false;
472    }
473    /* If already allocated, free first */
474    if (item->found && item->val.strval) {
475       free(item->val.strval);
476    }
477    item->val.strval = bstrdup(lc->str);
478    scan_to_eol(lc);
479    return true;
480 }
481
482 bool ini_store_name(LEX *lc, ConfigFile *inifile, ini_items *item)
483 {
484    if (!lc) {
485       Mmsg(inifile->edit, "%s", item->val.nameval);
486       return true;
487    }
488    if (lex_get_token(lc, T_NAME) == T_ERROR) {
489       return false;
490    }
491    strncpy(item->val.nameval, lc->str, sizeof(item->val.nameval));
492    scan_to_eol(lc);
493    return true;
494 }
495
496 bool ini_store_alist_str(LEX *lc, ConfigFile *inifile, ini_items *item)
497 {
498    alist *list;
499    if (!lc) {
500       /* TODO, write back the alist to edit buffer */
501       return true;
502    }
503    if (lex_get_token(lc, T_STRING) == T_ERROR) {
504       return false;
505    }
506
507    if (item->val.alistval == NULL) {
508       list = New(alist(10, owned_by_alist));
509    } else {
510       list = item->val.alistval;    
511    }
512
513    Dmsg4(900, "Append %s to alist %p size=%d %s\n", 
514          lc->str, list, list->size(), item->name);
515    list->append(bstrdup(lc->str));
516    item->val.alistval = list;
517
518    scan_to_eol(lc);
519    return true;
520 }
521
522 bool ini_store_pint64(LEX *lc, ConfigFile *inifile, ini_items *item)
523 {
524    if (!lc) {
525       Mmsg(inifile->edit, "%lld", item->val.int64val);
526       return true;
527    }
528    if (lex_get_token(lc, T_PINT64) == T_ERROR) {
529       return false;
530    }
531    item->val.int64val = lc->pint64_val;
532    scan_to_eol(lc);
533    return true;
534 }
535
536 bool ini_store_int64(LEX *lc, ConfigFile *inifile, ini_items *item)
537 {
538    if (!lc) {
539       Mmsg(inifile->edit, "%lld", item->val.int64val);
540       return true;
541    }
542    if (lex_get_token(lc, T_INT64) == T_ERROR) {
543       return false;
544    }
545    item->val.int64val = lc->int64_val;
546    scan_to_eol(lc);
547    return true;
548 }
549
550 bool ini_store_pint32(LEX *lc, ConfigFile *inifile, ini_items *item)
551 {
552    if (!lc) {
553       Mmsg(inifile->edit, "%d", item->val.int32val);
554       return true;
555    }
556    if (lex_get_token(lc, T_PINT32) == T_ERROR) {
557       return false;
558    }
559    item->val.int32val = lc->pint32_val;
560    scan_to_eol(lc);
561    return true;
562 }
563
564 bool ini_store_int32(LEX *lc, ConfigFile *inifile, ini_items *item)
565 {
566    if (!lc) {
567       Mmsg(inifile->edit, "%d", item->val.int32val);
568       return true;
569    }
570    if (lex_get_token(lc, T_INT32) == T_ERROR) {
571       return false;
572    }
573    item->val.int32val = lc->int32_val;
574    scan_to_eol(lc);
575    return true;
576 }
577
578 bool ini_store_bool(LEX *lc, ConfigFile *inifile, ini_items *item)
579 {
580    if (!lc) {
581       Mmsg(inifile->edit, "%s", item->val.boolval?"yes":"no");
582       return true;
583    }
584    if (lex_get_token(lc, T_NAME) == T_ERROR) {
585       return false;
586    }
587    if (strcasecmp(lc->str, "yes") == 0 || strcasecmp(lc->str, "true") == 0) {
588       item->val.boolval = true;
589    } else if (strcasecmp(lc->str, "no") == 0 || strcasecmp(lc->str, "false") == 0) {
590       item->val.boolval = false;
591    } else {
592       /* YES and NO must not be translated */
593       scan_err2(lc, _("Expect %s, got: %s"), "YES, NO, TRUE, or FALSE", lc->str);
594       return false;
595    }
596    scan_to_eol(lc);
597    return true;
598 }
599
600 /* ---------------------------------------------------------------- */
601 #ifdef TEST_PROGRAM
602 /* make ini
603  * export LD_LIBRARY_PATH=.libs/
604  * ./.libs/ini
605  */
606
607 #include <stdio.h>
608
609 int err=0;
610 int nb=0;
611 void _ok(const char *file, int l, const char *op, int value, const char *label)
612 {
613    nb++;
614    if (!value) {
615       err++;
616       printf("ERR %.45s %s:%i on %s\n", label, file, l, op);
617    } else {
618       printf("OK  %.45s\n", label);
619    }
620 }
621
622 #define ok(x, label) _ok(__FILE__, __LINE__, #x, (x), label)
623
624 void _nok(const char *file, int l, const char *op, int value, const char *label)
625 {
626    nb++;
627    if (value) {
628       err++;
629       printf("ERR %.45s %s:%i on !%s\n", label, file, l, op);
630    } else {
631       printf("OK  %.45s\n", label);
632    }
633 }
634
635 #define nok(x, label) _nok(__FILE__, __LINE__, #x, (x), label)
636
637 int report()
638 {
639    printf("Result %i/%i OK\n", nb - err, nb);
640    return err>0;
641 }
642
643 struct ini_items test_items[] = {
644    /* name          handler           comment             req */
645    {"datastore",   ini_store_name,    "Target Datastore", 0},
646    {"newhost",     ini_store_str,     "New Hostname",     1},
647    {"int64val",    ini_store_int64,   "Int64",            1},
648    {"list",        ini_store_alist_str, "list",           0},
649    {"bool",        ini_store_bool,    "Bool",             0},
650    {"pint64",      ini_store_pint64,  "pint",             0},
651    {"int32",       ini_store_int32,   "int 32bit",        0},
652    {"plugin.test", ini_store_str,     "test with .",      0},
653    {NULL,          NULL,              NULL,               0}
654 };
655
656 int main()
657 {
658    FILE *fp;
659    int pos;
660    ConfigFile *ini = new ConfigFile();
661    POOLMEM *buf = get_pool_memory(PM_BSOCK);
662
663    nok(ini->register_items(test_items, 5), "Check bad sizeof ini_items");
664    ok(ini->register_items(test_items, sizeof(struct ini_items)), "Check sizeof ini_items");
665
666    if ((fp = fopen("test.cfg", "w")) == NULL) {
667       exit (1);
668    }
669    fprintf(fp, "# this is a comment\ndatastore=datastore1\nnewhost=\"host1\"\n");
670    fflush(fp);
671
672    nok(ini->parse("test.cfg"), "Test missing member");
673    ini->clear_items();
674
675    fprintf(fp, "int64val=12 # with a comment\n");
676    fprintf(fp, "int64val=10 # with a comment\n");
677    fprintf(fp, "int32=100\n");
678    fprintf(fp, "bool=yes\n");
679    fprintf(fp, "plugin.test=parameter\n");
680
681    fflush(fp);
682
683    ok(ini->parse("test.cfg"), "Test with all members");
684
685    ok(ini->items[0].found, "Test presence of char[]");
686    ok(!strcmp(ini->items[0].val.nameval, "datastore1"), "Test char[]");
687    ok(ini->items[1].found, "Test presence of char*");
688    ok(!strcmp(ini->items[1].val.strval, "host1"), "Test char*");
689    ok(ini->items[2].found, "Test presence of int");
690    ok(ini->items[2].val.int64val == 10, "Test int");
691    ok(ini->items[4].val.boolval == true, "Test bool");
692    ok(ini->items[6].val.int32val == 100, "Test int 32");
693
694    alist *list = ini->items[3].val.alistval;
695    nok(ini->items[3].found, "Test presence of alist");
696
697    fprintf(fp, "list=a\nlist=b\nlist=c\n");
698    fflush(fp);
699
700    ini->clear_items();
701    ok(ini->parse("test.cfg"), "Test with all members");
702
703    list = ini->items[3].val.alistval;
704    ok(ini->items[3].found, "Test presence of alist");
705    ok(list != NULL, "Test list member");
706    ok(list->size() == 3, "Test list size");
707
708    ok(!strcmp((char *)list->get(0), "a"), "Testing alist[0]");
709    ok(!strcmp((char *)list->get(1), "b"), "Testing alist[1]");
710    ok(!strcmp((char *)list->get(2), "c"), "Testing alist[2]");
711
712    system("cp -f test.cfg test3.cfg");
713
714    fprintf(fp, "pouet='10, 11, 12'\n");
715    fprintf(fp, "pint=-100\n");
716    fprintf(fp, "int64val=-100\n"); /* TODO: fix negative numbers */
717    fflush(fp);
718
719    ini->clear_items();
720    ok(ini->parse("test.cfg"), "Test with errors");
721    nok(ini->items[5].found, "Test presence of positive int");
722
723    fclose(fp);
724    ini->clear_items();
725    ini->free_items();
726    
727    /* Test  */
728    if ((fp = fopen("test2.cfg", "w")) == NULL) {
729       exit (1);
730    }
731    fprintf(fp, 
732            "# this is a comment\n"
733            "optprompt=\"Datastore Name\"\n"
734            "datastore=@NAME@\n"
735            "optprompt=\"New Hostname to create\"\n"
736            "newhost=@STR@\n"
737            "optprompt=\"Some 64 integer\"\n"
738            "optrequired=yes\n"
739            "int64val=@INT64@\n"
740            "list=@ALIST@\n"
741            "bool=@BOOL@\n"
742            "pint64=@PINT64@\n"
743            "pouet=@STR@\n"
744            "int32=@INT32@\n"
745            "plugin.test=@STR@\n"
746       );
747    fclose(fp);
748
749    ok(ini->unserialize("test2.cfg"), "Test dynamic parse");
750    ok(ini->serialize("test4.cfg"), "Try to dump the item table in a file");
751    ok(ini->serialize(&buf) > 0, "Try to dump the item table in a buffer");
752    ok(ini->parse("test3.cfg"), "Parse test file with dynamic grammar");
753
754    ok((pos = ini->get_item("datastore")) == 0, "Check datastore definition");
755    ok(ini->items[pos].found, "Test presence of char[]");
756    ok(!strcmp(ini->items[pos].val.nameval, "datastore1"), "Test char[]");
757    ok(!strcmp(ini->items[pos].comment, "Datastore Name"), "Check comment");
758    ok(ini->items[pos].required == false, "Check required");
759
760    ok((pos = ini->get_item("newhost")) == 1, "Check newhost definition");
761    ok(ini->items[pos].found, "Test presence of char*");
762    ok(!strcmp(ini->items[pos].val.strval, "host1"), "Test char*");
763    ok(ini->items[pos].required == false, "Check required");
764
765    ok((pos = ini->get_item("int64val")) == 2, "Check int64val definition");
766    ok(ini->items[pos].found, "Test presence of int");
767    ok(ini->items[pos].val.int64val == 10, "Test int");
768    ok(ini->items[pos].required == true, "Check required");
769
770    ok((pos = ini->get_item("bool")) == 4, "Check bool definition");   
771    ok(ini->items[pos].val.boolval == true, "Test bool");
772
773    ok(ini->dump_results(&buf), "Test to dump results");
774    printf("<%s>\n", buf);
775
776    ini->clear_items();
777    ini->free_items();
778    report();
779    
780    free_pool_memory(buf);
781    exit (0);
782 }
783
784
785 #endif