]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/ini.c
Add support for items with comma in ini_store_alist_str()
[bacula/bacula] / bacula / src / lib / ini.c
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2017 Kern Sibbald
5
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13
14    This notice must be preserved when any source code is 
15    conveyed and/or propagated.
16
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20  * Handle simple configuration file such as "ini" files.
21  * key1 = val     # comment
22  * key2 = val     # <type>
23  *
24  */
25
26 #include "bacula.h"
27 #include "ini.h"
28
29 #define bfree_and_null_const(a) do{if(a){free((void *)a); (a)=NULL;}} while(0)
30 static int dbglevel = 100;
31
32 /* We use this structure to associate a key to the function */
33 struct ini_store {
34    const char *key;
35    const char *comment;
36    INI_ITEM_HANDLER *handler;
37 };
38
39 static struct ini_store funcs[] = {
40    {"@INT32@",  "Integer",          ini_store_int32},
41    {"@PINT32@", "Integer",          ini_store_pint32},
42    {"@PINT64@", "Positive Integer", ini_store_pint64},
43    {"@INT64@",  "Integer",          ini_store_int64},
44    {"@NAME@",   "Simple String",    ini_store_name},
45    {"@STR@",    "String",           ini_store_str},
46    {"@BOOL@",   "on/off",           ini_store_bool},
47    {"@ALIST@",  "String list",      ini_store_alist_str},
48    {"@DATE@",   "Date",             ini_store_date},
49 /* TODO: Add protocol for the FD @ASKFD@ */
50    {NULL,       NULL,               NULL}
51 };
52
53 /*
54  * Get handler code from handler @
55  */
56 const char *ini_get_store_code(INI_ITEM_HANDLER *handler)
57 {
58    for (int i = 0; funcs[i].key ; i++) {
59       if (funcs[i].handler == handler) {
60          return funcs[i].key;
61       }
62    }
63    return NULL;
64 }
65
66 /*
67  * Get handler function from handler name
68  */
69 INI_ITEM_HANDLER *ini_get_store_handler(const char *key)
70 {
71    for (int i = 0; funcs[i].key ; i++) {
72       if (!strcmp(funcs[i].key, key)) {
73          return funcs[i].handler;
74       }
75    }
76    return NULL;
77 }
78
79 /*
80  * Format a scanner error message
81  */
82 static void s_err(const char *file, int line, LEX *lc, const char *msg, ...)
83 {
84    ConfigFile *ini = (ConfigFile *)(lc->caller_ctx);
85    va_list arg_ptr;
86    char buf[MAXSTRING];
87
88    va_start(arg_ptr, msg);
89    bvsnprintf(buf, sizeof(buf), msg, arg_ptr);
90    va_end(arg_ptr);
91
92 #ifdef TEST_PROGRAM
93    printf("ERROR: Config file error: %s\n"
94           "            : Line %d, col %d of file %s\n%s\n",
95       buf, lc->line_no, lc->col_no, lc->fname, lc->line);
96 #endif
97
98    if (ini->jcr) {              /* called from core */
99       Jmsg(ini->jcr, M_ERROR, 0, _("Config file error: %s\n"
100                               "            : Line %d, col %d of file %s\n%s\n"),
101          buf, lc->line_no, lc->col_no, lc->fname, lc->line);
102
103 //   } else if (ini->ctx) {       /* called from plugin */
104 //      ini->bfuncs->JobMessage(ini->ctx, __FILE__, __LINE__, M_FATAL, 0,
105 //                    _("Config file error: %s\n"
106 //                      "            : Line %d, col %d of file %s\n%s\n"),
107 //                            buf, lc->line_no, lc->col_no, lc->fname, lc->line);
108 //
109    } else {                     /* called from ??? */
110       e_msg(file, line, M_ERROR, 0,
111             _("Config file error: %s\n"
112               "            : Line %d, col %d of file %s\n%s\n"),
113             buf, lc->line_no, lc->col_no, lc->fname, lc->line);
114    }
115 }
116
117 /* Reset free items */
118 void ConfigFile::clear_items()
119 {
120    if (!items) {
121       return;
122    }
123
124    for (int i=0; items[i].name; i++) {
125       if (items[i].found) {
126          /* special members require delete or free */
127          if (items[i].handler == ini_store_str) {
128             free(items[i].val.strval);
129             items[i].val.strval = NULL;
130
131          } else if (items[i].handler == ini_store_alist_str) {
132             delete items[i].val.alistval;
133             items[i].val.alistval = NULL;
134          }
135          items[i].found = false;
136       }
137    }
138 }
139
140 void ConfigFile::free_items()
141 {
142    if (items_allocated) {
143       for (int i=0; items[i].name; i++) {
144          bfree_and_null_const(items[i].name);
145          bfree_and_null_const(items[i].comment);
146          bfree_and_null_const(items[i].default_value);
147       }
148    }
149    if (items) {
150       free(items);
151    }
152    items = NULL;
153    items_allocated = false;
154 }
155
156 /* Get a particular item from the items list */
157 int ConfigFile::get_item(const char *name)
158 {
159    if (!items) {
160       return -1;
161    }
162
163    for (int i=0; i < MAX_INI_ITEMS && items[i].name; i++) {
164       if (strcasecmp(name, items[i].name) == 0) {
165          return i;
166       }
167    }
168    return -1;
169 }
170
171 /* Dump a buffer to a file in the working directory
172  * Needed to unserialise() a config
173  */
174 bool ConfigFile::dump_string(const char *buf, int32_t len)
175 {
176    FILE *fp;
177    bool ret=false;
178
179    if (!out_fname) {
180       out_fname = get_pool_memory(PM_FNAME);
181       make_unique_filename(&out_fname, (int)(intptr_t)this, (char*)"configfile");
182    }
183
184    fp = bfopen(out_fname, "wb");
185    if (!fp) {
186       return ret;
187    }
188
189    if (fwrite(buf, len, 1, fp) == 1) {
190       ret = true;
191    }
192
193    fclose(fp);
194    return ret;
195 }
196
197 /* Dump the item table format to a text file (used by plugin) */
198 bool ConfigFile::serialize(const char *fname)
199 {
200    FILE *fp;
201    POOLMEM *tmp;
202    int32_t len;
203    bool ret = false;
204
205    if (!items) {
206       return ret;
207    }
208
209    fp = bfopen(fname, "w");
210    if (!fp) {
211       return ret;
212    }
213
214    tmp = get_pool_memory(PM_MESSAGE);
215    len = serialize(&tmp);
216    if (fwrite(tmp, len, 1, fp) == 1) {
217       ret = true;
218    }
219    free_pool_memory(tmp);
220
221    fclose(fp);
222    return ret;
223 }
224
225 /* Dump the item table format to a text file (used by plugin) */
226 int ConfigFile::serialize(POOLMEM **buf)
227 {
228    int len;
229    POOLMEM *tmp, *tmp2;
230    if (!items) {
231       **buf = 0;
232       return 0;
233    }
234
235    len = Mmsg(buf, "# Plugin configuration file\n# Version %d\n", version);
236
237    tmp = get_pool_memory(PM_MESSAGE);
238    tmp2 = get_pool_memory(PM_MESSAGE);
239
240    for (int i=0; items[i].name ; i++) {
241       if (items[i].comment) {
242          Mmsg(tmp, "OptPrompt=%s\n", quote_string(tmp2, items[i].comment));
243          pm_strcat(buf, tmp);
244       }
245       if (items[i].default_value) {
246          Mmsg(tmp, "OptDefault=%s\n",
247               quote_string(tmp2, items[i].default_value));
248          pm_strcat(buf, tmp);
249       }
250       if (items[i].required) {
251          Mmsg(tmp, "OptRequired=yes\n");
252          pm_strcat(buf, tmp);
253       }
254
255       Mmsg(tmp, "%s=%s\n\n",
256            items[i].name, ini_get_store_code(items[i].handler));
257
258       /* variable = @INT64@ */
259       len = pm_strcat(buf, tmp);
260    }
261    free_pool_memory(tmp);
262    free_pool_memory(tmp2);
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, *tmp2;
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    tmp2 = get_pool_memory(PM_MESSAGE);
280
281    for (int i=0; items[i].name ; i++) {
282       bool process= items[i].found;
283       if (items[i].found) {
284          items[i].handler(NULL, this, &items[i]);
285       }
286       if (!items[i].found && items[i].required && items[i].default_value) {
287          pm_strcpy(this->edit, items[i].default_value);
288          process = true;
289       }
290       if (process) {
291          if (items[i].comment && *items[i].comment) {
292             Mmsg(tmp, "# %s\n", items[i].comment);
293             pm_strcat(buf, tmp);
294          }
295          if (items[i].handler == ini_store_str  ||
296              items[i].handler == ini_store_name ||
297              items[i].handler == ini_store_date)
298          {
299             Mmsg(tmp, "%s=%s\n\n",
300                  items[i].name,
301                  quote_string(tmp2, this->edit));
302
303          } else {
304             Mmsg(tmp, "%s=%s\n\n", items[i].name, this->edit);
305          }
306          len = pm_strcat(buf, tmp);
307       }
308    }
309    free_pool_memory(tmp);
310    free_pool_memory(tmp2);
311
312    return len ;
313 }
314
315 bool ConfigFile::parse()
316 {
317    int token, i;
318    bool ret = false;
319    bool found;
320
321    lc->options |= LOPT_NO_EXTERN;
322    lc->caller_ctx = (void *)this;
323
324    while ((token=lex_get_token(lc, T_ALL)) != T_EOF) {
325       if (token == T_EOL) {
326          continue;
327       }
328       found = false;
329       for (i=0; items[i].name; i++) {
330          if (strcasecmp(items[i].name, lc->str) == 0) {
331             if ((token = lex_get_token(lc, T_EQUALS)) == T_ERROR) {
332                Dmsg2(dbglevel, "in T_IDENT got token=%s str=%s\n", lex_tok_to_str(token), lc->str);
333                break;
334             }
335             Dmsg2(dbglevel, "parse got token=%s str=%s\n", lex_tok_to_str(token), lc->str);
336             Dmsg1(dbglevel, "calling handler for %s\n", items[i].name);
337             /* Call item handler */
338             ret = items[i].found = items[i].handler(lc, this, &items[i]);
339             found = true;
340             break;
341          }
342       }
343       if (!found) {
344          Dmsg1(dbglevel, "Unfound keyword=%s\n", lc->str);
345          scan_err1(lc, "Keyword %s not found", lc->str);
346          /* We can raise an error here */
347          break;
348       } else {
349          Dmsg1(dbglevel, "Found keyword=%s\n", items[i].name);
350       }
351       if (!ret) {
352          Dmsg1(dbglevel, "Error getting value for keyword=%s\n", items[i].name);
353          break;
354       }
355       Dmsg0(dbglevel, "Continue with while(token) loop\n");
356    }
357
358    for (i=0; items[i].name; i++) {
359       if (items[i].required && !items[i].found) {
360          scan_err1(lc, "%s required but not found", items[i].name);
361          ret = false;
362       }
363    }
364
365    lc = lex_close_file(lc);
366    return ret;
367 }
368
369 /* Parse a config file used by Plugin/Director */
370 bool ConfigFile::parse(const char *fname)
371 {
372    if (!items) {
373       return false;
374    }
375
376    if ((lc = lex_open_file(lc, fname, s_err)) == NULL) {
377       berrno be;
378       Emsg2(M_ERROR, 0, _("Cannot open config file %s: %s\n"),
379             fname, be.bstrerror());
380       return false;
381    }
382    return parse();          /* Parse file */
383 }
384
385 /* Parse a config buffer used by Plugin/Director */
386 bool ConfigFile::parse_buf(const char *buffer)
387 {
388    if (!items) {
389       return false;
390    }
391
392    if ((lc = lex_open_buf(lc, buffer, s_err)) == NULL) {
393       Emsg0(M_ERROR, 0, _("Cannot open lex\n"));
394       return false;
395    }
396    return parse();         /* Parse memory buffer */
397 }
398
399
400 /* Analyse the content of a ini file to build the item list
401  * It uses special syntax for datatype. Used by Director on Restore object
402  *
403  * OptPrompt = "Variable1"
404  * OptRequired
405  * OptDefault = 100
406  * Variable1 = @PINT32@
407  * ...
408  */
409 bool ConfigFile::unserialize(const char *fname)
410 {
411    int token, i, nb = 0;
412    bool ret=false;
413    const char **assign;
414
415    /* At this time, we allow only 32 different items */
416    int s = MAX_INI_ITEMS * sizeof (struct ini_items);
417
418    items = (struct ini_items *) malloc (s);
419    memset(items, 0, s);
420    items_allocated = true;
421
422    /* parse the file and generate the items structure on the fly */
423    if ((lc = lex_open_file(lc, fname, s_err)) == NULL) {
424       berrno be;
425       Emsg2(M_ERROR, 0, _("Cannot open config file %s: %s\n"),
426             fname, be.bstrerror());
427       return false;
428    }
429    lc->options |= LOPT_NO_EXTERN;
430    lc->caller_ctx = (void *)this;
431
432    while ((token=lex_get_token(lc, T_ALL)) != T_EOF) {
433       Dmsg1(dbglevel, "parse got token=%s\n", lex_tok_to_str(token));
434
435       if (token == T_EOL) {
436          continue;
437       }
438
439       ret = false;
440       assign = NULL;
441
442       if (nb >= MAX_INI_ITEMS) {
443          break;
444       }
445
446       if (strcasecmp("optprompt", lc->str) == 0) {
447          assign = &(items[nb].comment);
448
449       } else if (strcasecmp("optdefault", lc->str) == 0) {
450          assign = &(items[nb].default_value);
451
452       } else if (strcasecmp("optrequired", lc->str) == 0) {
453          items[nb].required = true;               /* Don't use argument */
454          scan_to_eol(lc);
455          continue;
456
457       } else {
458          items[nb].name = bstrdup(lc->str);
459       }
460
461       token = lex_get_token(lc, T_ALL);
462       Dmsg1(dbglevel, "in T_IDENT got token=%s\n", lex_tok_to_str(token));
463
464       if (token != T_EQUALS) {
465          scan_err1(lc, "expected an equals, got: %s", lc->str);
466          break;
467       }
468
469       /* We may allow blank variable */
470       if (lex_get_token(lc, T_STRING) == T_ERROR) {
471          break;
472       }
473
474       if (assign) {
475          *assign = bstrdup(lc->str);
476
477       } else {
478          if ((items[nb].handler = ini_get_store_handler(lc->str)) == NULL) {
479             scan_err1(lc, "expected a data type, got: %s", lc->str);
480             break;
481          }
482          nb++;
483       }
484       scan_to_eol(lc);
485       ret = true;
486    }
487
488    if (!ret) {
489       for (i = 0; i < nb ; i++) {
490          bfree_and_null_const(items[i].name);
491          bfree_and_null_const(items[i].comment);
492          bfree_and_null_const(items[i].default_value);
493          items[i].handler = NULL;
494          items[i].required = false;
495       }
496    }
497
498    lc = lex_close_file(lc);
499    return ret;
500 }
501
502 /* ----------------------------------------------------------------
503  * Handle data type. Import/Export
504  * ----------------------------------------------------------------
505  */
506 bool ini_store_str(LEX *lc, ConfigFile *inifile, ini_items *item)
507 {
508    if (!lc) {
509       Mmsg(inifile->edit, "%s", item->val.strval);
510       return true;
511    }
512    if (lex_get_token(lc, T_STRING) == T_ERROR) {
513       return false;
514    }
515    /* If already allocated, free first */
516    if (item->found && item->val.strval) {
517       free(item->val.strval);
518    }
519    item->val.strval = bstrdup(lc->str);
520    scan_to_eol(lc);
521    return true;
522 }
523
524 bool ini_store_name(LEX *lc, ConfigFile *inifile, ini_items *item)
525 {
526    if (!lc) {
527       Mmsg(inifile->edit, "%s", item->val.nameval);
528       return true;
529    }
530    if (lex_get_token(lc, T_NAME) == T_ERROR) {
531       Dmsg0(dbglevel, "Want token=T_NAME got T_ERROR\n");
532       return false;
533    }
534    Dmsg1(dbglevel, "ini_store_name: %s\n", lc->str);
535    strncpy(item->val.nameval, lc->str, sizeof(item->val.nameval));
536    scan_to_eol(lc);
537    return true;
538 }
539
540 bool ini_store_alist_str(LEX *lc, ConfigFile *inifile, ini_items *item)
541 {
542    alist *list = item->val.alistval;
543    if (!lc) {
544       /* TODO, write back the alist to edit buffer */
545       return true;
546    }
547
548    for (;;) {
549       if (lex_get_token(lc, T_STRING) == T_ERROR) {
550          return false;
551       }
552
553       if (list == NULL) {
554          list = New(alist(10, owned_by_alist));
555       }
556       list->append(bstrdup(lc->str));
557
558       if (lc->ch != ',') {         /* if no other item follows */
559          if (!lex_check_eol(lc)) {
560             /* found garbage at the end of the line */
561             return false;
562          }
563          break;                    /* get out */
564       }
565       lex_get_token(lc, T_ALL);    /* eat comma */
566    }
567
568    item->val.alistval = list;
569    scan_to_eol(lc);
570    return true;
571 }
572
573 bool ini_store_pint64(LEX *lc, ConfigFile *inifile, ini_items *item)
574 {
575    if (!lc) {
576       Mmsg(inifile->edit, "%lld", item->val.int64val);
577       return true;
578    }
579    if (lex_get_token(lc, T_PINT64) == T_ERROR) {
580       return false;
581    }
582    item->val.int64val = lc->pint64_val;
583    scan_to_eol(lc);
584    return true;
585 }
586
587 bool ini_store_int64(LEX *lc, ConfigFile *inifile, ini_items *item)
588 {
589    if (!lc) {
590       Mmsg(inifile->edit, "%lld", item->val.int64val);
591       return true;
592    }
593    if (lex_get_token(lc, T_INT64) == T_ERROR) {
594       return false;
595    }
596    item->val.int64val = lc->int64_val;
597    scan_to_eol(lc);
598    return true;
599 }
600
601 bool ini_store_pint32(LEX *lc, ConfigFile *inifile, ini_items *item)
602 {
603    if (!lc) {
604       Mmsg(inifile->edit, "%d", item->val.int32val);
605       return true;
606    }
607    if (lex_get_token(lc, T_PINT32) == T_ERROR) {
608       return false;
609    }
610    item->val.int32val = lc->pint32_val;
611    scan_to_eol(lc);
612    return true;
613 }
614
615 bool ini_store_int32(LEX *lc, ConfigFile *inifile, ini_items *item)
616 {
617    if (!lc) {
618       Mmsg(inifile->edit, "%d", item->val.int32val);
619       return true;
620    }
621    if (lex_get_token(lc, T_INT32) == T_ERROR) {
622       return false;
623    }
624    item->val.int32val = lc->int32_val;
625    scan_to_eol(lc);
626    return true;
627 }
628
629 bool ini_store_bool(LEX *lc, ConfigFile *inifile, ini_items *item)
630 {
631    if (!lc) {
632       Mmsg(inifile->edit, "%s", item->val.boolval?"yes":"no");
633       return true;
634    }
635    if (lex_get_token(lc, T_NAME) == T_ERROR) {
636       return false;
637    }
638    if (strcasecmp(lc->str, "yes")  == 0 ||
639        strcasecmp(lc->str, "true") == 0 ||
640        strcasecmp(lc->str, "on")   == 0 ||
641        strcasecmp(lc->str, "1")    == 0)
642    {
643       item->val.boolval = true;
644
645    } else if (strcasecmp(lc->str, "no")    == 0 ||
646               strcasecmp(lc->str, "false") == 0 ||
647               strcasecmp(lc->str, "off")   == 0 ||
648               strcasecmp(lc->str, "0")     == 0)
649    {
650       item->val.boolval = false;
651
652    } else {
653       /* YES and NO must not be translated */
654       scan_err2(lc, _("Expect %s, got: %s"), "YES, NO, ON, OFF, 0, 1, TRUE, or FALSE", lc->str);
655       return false;
656    }
657    scan_to_eol(lc);
658    return true;
659 }
660
661 bool ini_store_date(LEX *lc, ConfigFile *inifile, ini_items *item)
662 {
663    if (!lc) {
664       bstrutime(inifile->edit,sizeof_pool_memory(inifile->edit),item->val.btimeval);
665       return true;
666    }
667    if (lex_get_token(lc, T_STRING) == T_ERROR) {
668       return false;
669    }
670    item->val.btimeval = str_to_utime(lc->str);
671    if (item->val.btimeval == 0) {
672       return false;
673    }
674    scan_to_eol(lc);
675    return true;
676 }
677
678 /* ---------------------------------------------------------------- */
679 #ifdef TEST_PROGRAM
680 /* make ini
681  * export LD_LIBRARY_PATH=.libs/
682  * ./.libs/ini
683  */
684
685 #include <stdio.h>
686
687 int err=0;
688 int nb=0;
689 void _ok(const char *file, int l, const char *op, int value, const char *label)
690 {
691    nb++;
692    if (!value) {
693       err++;
694       printf("ERR %.45s %s:%i on %s\n", label, file, l, op);
695    } else {
696       printf("OK  %.45s\n", label);
697    }
698 }
699
700 #define ok(x, label) _ok(__FILE__, __LINE__, #x, (x), label)
701
702 void _nok(const char *file, int l, const char *op, int value, const char *label)
703 {
704    nb++;
705    if (value) {
706       err++;
707       printf("ERR %.45s %s:%i on !%s\n", label, file, l, op);
708    } else {
709       printf("OK  %.45s\n", label);
710    }
711 }
712
713 #define nok(x, label) _nok(__FILE__, __LINE__, #x, (x), label)
714
715 int report()
716 {
717    printf("Result %i/%i OK\n", nb - err, nb);
718    return err>0;
719 }
720
721 struct ini_items membuf_items[] = {
722    /* name          handler           comment             req */
723    {"client",      ini_store_name,    "Client name",       0},
724    {"serial",      ini_store_int32,   "Serial number",     1},
725    {"max_clients", ini_store_int32,   "Max Clients",       0},
726    {NULL,          NULL,              NULL,                0}
727 };
728
729
730 struct ini_items test_items[] = {
731    /* name          handler           comment             req */
732    {"datastore",   ini_store_name,    "Target Datastore", 0},
733    {"newhost",     ini_store_str,     "New Hostname",     1},
734    {"int64val",    ini_store_int64,   "Int64",            1},
735    {"list",        ini_store_alist_str, "list",           0},
736    {"bool",        ini_store_bool,    "Bool",             0},
737    {"pint64",      ini_store_pint64,  "pint",             0},
738    {"int32",       ini_store_int32,   "int 32bit",        0},
739    {"plugin.test", ini_store_str,     "test with .",      0},
740    {"adate",       ini_store_date,    "test with date",   0},
741    {NULL,          NULL,              NULL,               0}
742 };
743
744 /* In order to link with libbaccfg */
745 int32_t r_last;
746 int32_t r_first;
747 RES_HEAD **res_head;
748 bool save_resource(RES_HEAD **rhead, int type, RES_ITEM *items, int pass){return false;}
749 bool save_resource(CONFIG*, int, RES_ITEM*, int) {return false;}
750 void dump_resource(int type, RES *ares, void sendit(void *sock, const char *fmt, ...), void *sock){}
751 void free_resource(RES *rres, int type){}
752 union URES {
753 };
754 RES_TABLE resources[] = {};
755 URES res_all;
756
757 int main()
758 {
759    FILE *fp;
760    int pos, size;
761    ConfigFile *ini = new ConfigFile();
762    POOLMEM *buf = get_pool_memory(PM_BSOCK);
763    char buffer[2000];
764
765    //debug_level=500;
766    printf("Begin Memory buffer Test\n");
767    ok(ini->register_items(membuf_items, sizeof(struct ini_items)), "Check sizeof ini_items");
768
769    if ((fp = fopen("test.cfg", "w")) == NULL) {
770       exit (1);
771    }
772    fprintf(fp, "client=JohnDoe\n");
773    fprintf(fp, "serial=2\n");
774    fprintf(fp, "max_clients=3\n");
775    fflush(fp);
776    fseek(fp, 0, SEEK_END);
777    size = ftell(fp);
778    fclose(fp);
779
780    if ((fp = fopen("test.cfg", "rb")) == NULL) {
781       printf("Could not open file test.cfg\n");
782       exit (1);
783    }
784
785    size = fread(buffer, 1, size, fp);
786    buffer[size] = 0;
787    //printf("size of read buffer is: %d\n", size);
788    //printf("====buf=%s\n", buffer);
789
790    ok(ini->parse_buf(buffer), "Test memory read with all members");
791
792    ini->clear_items();
793    fclose(fp);
794
795    //debug_level = 0;
796    printf("\n\nBegin Original Full Tests\n");
797    nok(ini->register_items(test_items, 5), "Check bad sizeof ini_items");
798    ok(ini->register_items(test_items, sizeof(struct ini_items)), "Check sizeof ini_items");
799
800    if ((fp = fopen("test.cfg", "w")) == NULL) {
801       exit (1);
802    }
803    fprintf(fp, "# this is a comment\ndatastore=datastore1\nnewhost=\"host1\"\n");
804    fflush(fp);
805
806    nok(ini->parse("test.cfg"), "Test missing member");
807    ini->clear_items();
808
809    fprintf(fp, "int64val=12 # with a comment\n");
810    fprintf(fp, "int64val=10 # with a comment\n");
811    fprintf(fp, "int32=100\n");
812    fprintf(fp, "bool=yes\n");
813    fprintf(fp, "plugin.test=parameter\n");
814    fprintf(fp, "adate=\"1970-01-02 12:00:00\"\n");
815
816    fflush(fp);
817
818    ok(ini->parse("test.cfg"), "Test with all members");
819
820    ok(ini->items[0].found, "Test presence of char[]");
821    ok(!strcmp(ini->items[0].val.nameval, "datastore1"), "Test char[]");
822    ok(ini->items[1].found, "Test presence of char*");
823    ok(!strcmp(ini->items[1].val.strval, "host1"), "Test char*");
824    ok(ini->items[2].found, "Test presence of int");
825    ok(ini->items[2].val.int64val == 10, "Test int");
826    ok(ini->items[4].val.boolval == true, "Test bool");
827    ok(ini->items[6].val.int32val == 100, "Test int 32");
828    ok(ini->items[6].val.btimeval != 126000, "Test btime");
829
830    alist *list = ini->items[3].val.alistval;
831    nok(ini->items[3].found, "Test presence of alist");
832
833    fprintf(fp, "list=a\nlist=b\nlist=c,d,e\n");
834    fflush(fp);
835
836    ini->clear_items();
837    ok(ini->parse("test.cfg"), "Test with all members");
838
839    list = ini->items[3].val.alistval;
840    ok(ini->items[3].found, "Test presence of alist");
841    ok(list != NULL, "Test list member");
842    ok(list->size() == 5, "Test list size");
843
844    ok(!strcmp((char *)list->get(0), "a"), "Testing alist[0]");
845    ok(!strcmp((char *)list->get(1), "b"), "Testing alist[1]");
846    ok(!strcmp((char *)list->get(2), "c"), "Testing alist[2]");
847
848    system("cp -f test.cfg test3.cfg");
849
850    fprintf(fp, "pouet='10, 11, 12'\n");
851    fprintf(fp, "pint=-100\n");
852    fprintf(fp, "int64val=-100\n"); /* TODO: fix negative numbers */
853    fflush(fp);
854
855    ini->clear_items();
856    ok(ini->parse("test.cfg"), "Test with errors");
857    nok(ini->items[5].found, "Test presence of positive int");
858
859    fclose(fp);
860    ini->clear_items();
861    ini->free_items();
862
863    /* Test  */
864    if ((fp = fopen("test2.cfg", "w")) == NULL) {
865       exit (1);
866    }
867    fprintf(fp,
868            "# this is a comment\n"
869            "optprompt=\"Datastore Name\"\n"
870            "datastore=@NAME@\n"
871            "optprompt=\"New Hostname to create\"\n"
872            "newhost=@STR@\n"
873            "optprompt=\"Some 64 integer\"\n"
874            "optrequired=yes\n"
875            "int64val=@INT64@\n"
876            "list=@ALIST@\n"
877            "bool=@BOOL@\n"
878            "pint64=@PINT64@\n"
879            "pouet=@STR@\n"
880            "int32=@INT32@\n"
881            "plugin.test=@STR@\n"
882            "adate=@DATE@\n"
883       );
884    fclose(fp);
885
886    ok(ini->unserialize("test2.cfg"), "Test dynamic parse");
887    ok(ini->serialize("test4.cfg"), "Try to dump the item table in a file");
888    ok(ini->serialize(&buf) > 0, "Try to dump the item table in a buffer");
889    ok(ini->parse("test3.cfg"), "Parse test file with dynamic grammar");
890
891    ok((pos = ini->get_item("datastore")) == 0, "Check datastore definition");
892    ok(ini->items[pos].found, "Test presence of char[]");
893    ok(!strcmp(ini->items[pos].val.nameval, "datastore1"), "Test char[]");
894    ok(!strcmp(ini->items[pos].comment, "Datastore Name"), "Check comment");
895    ok(ini->items[pos].required == false, "Check required");
896
897    ok((pos = ini->get_item("newhost")) == 1, "Check newhost definition");
898    ok(ini->items[pos].found, "Test presence of char*");
899    ok(!strcmp(ini->items[pos].val.strval, "host1"), "Test char*");
900    ok(ini->items[pos].required == false, "Check required");
901
902    ok((pos = ini->get_item("int64val")) == 2, "Check int64val definition");
903    ok(ini->items[pos].found, "Test presence of int");
904    ok(ini->items[pos].val.int64val == 10, "Test int");
905    ok(ini->items[pos].required == true, "Check required");
906
907    ok((pos = ini->get_item("bool")) == 4, "Check bool definition");
908    ok(ini->items[pos].val.boolval == true, "Test bool");
909
910    ok((pos = ini->get_item("adate")) == 9, "Check adate definition");
911    ok(ini->items[pos].val.btimeval == 126000, "Test date");
912
913    ok(ini->dump_results(&buf), "Test to dump results");
914    printf("<%s>\n", buf);
915
916    ini->clear_items();
917    ini->free_items();
918    report();
919
920    free_pool_memory(buf);
921    exit (0);
922 }
923
924
925 #endif