2 Copyright (C) 2011-2011 Bacula Systems(R) SA
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.
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.
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
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.
24 The licensor of Bacula Enterprise(TM) is Bacula Systems(R) SA,
25 Rue Galilee 5, 1400 Yverdon-les-Bains, Switzerland.
29 * Handle simple configuration file such as "ini" files.
30 * key1 = val # comment
38 #define bfree_and_null_const(a) do{if(a){free((void *)a); (a)=NULL;}} while(0)
39 static int dbglevel = 100;
41 /* We use this structure to associate a key to the function */
45 INI_ITEM_HANDLER *handler;
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},
61 * Get handler code from handler @
63 const char *ini_get_store_code(INI_ITEM_HANDLER *handler)
65 for (int i = 0; funcs[i].key ; i++) {
66 if (funcs[i].handler == handler) {
74 * Get handler function from handler name
76 INI_ITEM_HANDLER *ini_get_store_handler(const char *key)
78 for (int i = 0; funcs[i].key ; i++) {
79 if (!strcmp(funcs[i].key, key)) {
80 return funcs[i].handler;
87 * Format a scanner error message
89 static void s_err(const char *file, int line, LEX *lc, const char *msg, ...)
91 ConfigFile *ini = (ConfigFile *)(lc->caller_ctx);
95 va_start(arg_ptr, msg);
96 bvsnprintf(buf, sizeof(buf), msg, arg_ptr);
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);
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);
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);
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);
124 /* Reset free items */
125 void ConfigFile::clear_items()
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;
138 } else if (items[i].handler == ini_store_alist_str) {
139 delete items[i].val.alistval;
140 items[i].val.alistval = NULL;
142 items[i].found = false;
147 void ConfigFile::free_items()
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);
157 items_allocated = false;
160 /* Get a particular item from the items list */
161 int ConfigFile::get_item(const char *name)
167 for (int i=0; i < MAX_INI_ITEMS && items[i].name; i++) {
168 if (strcasecmp(name, items[i].name) == 0) {
175 /* Dump a buffer to a file in the working directory
176 * Needed to unserialise() a config
178 bool ConfigFile::dump_string(const char *buf, int32_t len)
184 out_fname = get_pool_memory(PM_FNAME);
185 make_unique_filename(&out_fname, (int)(intptr_t)this, (char*)"configfile");
188 fp = fopen(out_fname, "wb");
193 if (fwrite(buf, len, 1, fp) == 1) {
201 /* Dump the item table format to a text file (used by plugin) */
202 bool ConfigFile::serialize(const char *fname)
213 fp = fopen(fname, "w");
218 tmp = get_pool_memory(PM_MESSAGE);
219 len = serialize(&tmp);
220 if (fwrite(tmp, len, 1, fp) == 1) {
223 free_pool_memory(tmp);
229 /* Dump the item table format to a text file (used by plugin) */
230 int ConfigFile::serialize(POOLMEM **buf)
239 len = Mmsg(buf, "# Plugin configuration file\n# Version %d\n", version);
241 tmp = get_pool_memory(PM_MESSAGE);
243 for (int i=0; items[i].name ; i++) {
244 if (items[i].comment) {
245 Mmsg(tmp, "OptPrompt=%s\n", items[i].comment);
248 if (items[i].default_value) {
249 Mmsg(tmp, "OptDefault=%s\n", items[i].default_value);
252 if (items[i].required) {
253 Mmsg(tmp, "OptRequired=yes\n");
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);
262 free_pool_memory(tmp);
267 /* Dump the item table content to a text file (used by director) */
268 int ConfigFile::dump_results(POOLMEM **buf)
276 len = Mmsg(buf, "# Plugin configuration file\n# Version %d\n", version);
278 tmp = get_pool_memory(PM_MESSAGE);
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);
287 Mmsg(tmp, "%s=%s\n\n", items[i].name, this->edit);
288 len = pm_strcat(buf, tmp);
291 free_pool_memory(tmp);
296 /* Parse a config file used by Plugin/Director */
297 bool ConfigFile::parse(const char *fname)
306 if ((lc = lex_open_file(lc, fname, s_err)) == NULL) {
308 Emsg2(M_ERROR, 0, _("Cannot open config file %s: %s\n"),
309 fname, be.bstrerror());
312 lc->options |= LOPT_NO_EXTERN;
313 lc->caller_ctx = (void *)this;
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) {
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));
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]);
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 */
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);
353 lc = lex_close_file(lc);
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
361 * OptPrompt = "Variable1"
364 * Variable1 = @PINT32@
367 bool ConfigFile::unserialize(const char *fname)
369 int token, i, nb = 0;
373 /* At this time, we allow only 32 different items */
374 int s = MAX_INI_ITEMS * sizeof (struct ini_items);
376 items = (struct ini_items *) malloc (s);
378 items_allocated = true;
380 /* parse the file and generate the items structure on the fly */
381 if ((lc = lex_open_file(lc, fname, s_err)) == NULL) {
383 Emsg2(M_ERROR, 0, _("Cannot open config file %s: %s\n"),
384 fname, be.bstrerror());
387 lc->options |= LOPT_NO_EXTERN;
388 lc->caller_ctx = (void *)this;
390 while ((token=lex_get_token(lc, T_ALL)) != T_EOF) {
391 Dmsg1(dbglevel, "parse got token=%s\n", lex_tok_to_str(token));
393 if (token == T_EOL) {
400 if (nb >= MAX_INI_ITEMS) {
404 if (strcasecmp("optprompt", lc->str) == 0) {
405 assign = &(items[nb].comment);
407 } else if (strcasecmp("optdefault", lc->str) == 0) {
408 assign = &(items[nb].default_value);
410 } else if (strcasecmp("optrequired", lc->str) == 0) {
411 items[nb].required = true; /* Don't use argument */
416 items[nb].name = bstrdup(lc->str);
419 token = lex_get_token(lc, T_ALL);
420 Dmsg1(dbglevel, "in T_IDENT got token=%s\n", lex_tok_to_str(token));
422 if (token != T_EQUALS) {
423 scan_err1(lc, "expected an equals, got: %s", lc->str);
427 /* We may allow blank variable */
428 if (lex_get_token(lc, T_STRING) == T_ERROR) {
433 *assign = bstrdup(lc->str);
436 if ((items[nb].handler = ini_get_store_handler(lc->str)) == NULL) {
437 scan_err1(lc, "expected a data type, got: %s", lc->str);
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;
456 lc = lex_close_file(lc);
460 /* ----------------------------------------------------------------
461 * Handle data type. Import/Export
462 * ----------------------------------------------------------------
464 bool ini_store_str(LEX *lc, ConfigFile *inifile, ini_items *item)
467 Mmsg(inifile->edit, "%s", item->val.strval);
470 if (lex_get_token(lc, T_STRING) == T_ERROR) {
473 /* If already allocated, free first */
474 if (item->found && item->val.strval) {
475 free(item->val.strval);
477 item->val.strval = bstrdup(lc->str);
482 bool ini_store_name(LEX *lc, ConfigFile *inifile, ini_items *item)
485 Mmsg(inifile->edit, "%s", item->val.nameval);
488 if (lex_get_token(lc, T_NAME) == T_ERROR) {
491 strncpy(item->val.nameval, lc->str, sizeof(item->val.nameval));
496 bool ini_store_alist_str(LEX *lc, ConfigFile *inifile, ini_items *item)
500 /* TODO, write back the alist to edit buffer */
503 if (lex_get_token(lc, T_STRING) == T_ERROR) {
507 if (item->val.alistval == NULL) {
508 list = New(alist(10, owned_by_alist));
510 list = item->val.alistval;
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;
522 bool ini_store_pint64(LEX *lc, ConfigFile *inifile, ini_items *item)
525 Mmsg(inifile->edit, "%lld", item->val.int64val);
528 if (lex_get_token(lc, T_PINT64) == T_ERROR) {
531 item->val.int64val = lc->pint64_val;
536 bool ini_store_int64(LEX *lc, ConfigFile *inifile, ini_items *item)
539 Mmsg(inifile->edit, "%lld", item->val.int64val);
542 if (lex_get_token(lc, T_INT64) == T_ERROR) {
545 item->val.int64val = lc->int64_val;
550 bool ini_store_pint32(LEX *lc, ConfigFile *inifile, ini_items *item)
553 Mmsg(inifile->edit, "%d", item->val.int32val);
556 if (lex_get_token(lc, T_PINT32) == T_ERROR) {
559 item->val.int32val = lc->pint32_val;
564 bool ini_store_int32(LEX *lc, ConfigFile *inifile, ini_items *item)
567 Mmsg(inifile->edit, "%d", item->val.int32val);
570 if (lex_get_token(lc, T_INT32) == T_ERROR) {
573 item->val.int32val = lc->int32_val;
578 bool ini_store_bool(LEX *lc, ConfigFile *inifile, ini_items *item)
581 Mmsg(inifile->edit, "%s", item->val.boolval?"yes":"no");
584 if (lex_get_token(lc, T_NAME) == T_ERROR) {
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;
592 /* YES and NO must not be translated */
593 scan_err2(lc, _("Expect %s, got: %s"), "YES, NO, TRUE, or FALSE", lc->str);
600 /* ---------------------------------------------------------------- */
603 * export LD_LIBRARY_PATH=.libs/
611 void _ok(const char *file, int l, const char *op, int value, const char *label)
616 printf("ERR %.45s %s:%i on %s\n", label, file, l, op);
618 printf("OK %.45s\n", label);
622 #define ok(x, label) _ok(__FILE__, __LINE__, #x, (x), label)
624 void _nok(const char *file, int l, const char *op, int value, const char *label)
629 printf("ERR %.45s %s:%i on !%s\n", label, file, l, op);
631 printf("OK %.45s\n", label);
635 #define nok(x, label) _nok(__FILE__, __LINE__, #x, (x), label)
639 printf("Result %i/%i OK\n", nb - err, nb);
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}
660 ConfigFile *ini = new ConfigFile();
661 POOLMEM *buf = get_pool_memory(PM_BSOCK);
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");
666 if ((fp = fopen("test.cfg", "w")) == NULL) {
669 fprintf(fp, "# this is a comment\ndatastore=datastore1\nnewhost=\"host1\"\n");
672 nok(ini->parse("test.cfg"), "Test missing member");
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");
683 ok(ini->parse("test.cfg"), "Test with all members");
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");
694 alist *list = ini->items[3].val.alistval;
695 nok(ini->items[3].found, "Test presence of alist");
697 fprintf(fp, "list=a\nlist=b\nlist=c\n");
701 ok(ini->parse("test.cfg"), "Test with all members");
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");
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]");
712 system("cp -f test.cfg test3.cfg");
714 fprintf(fp, "pouet='10, 11, 12'\n");
715 fprintf(fp, "pint=-100\n");
716 fprintf(fp, "int64val=-100\n"); /* TODO: fix negative numbers */
720 ok(ini->parse("test.cfg"), "Test with errors");
721 nok(ini->items[5].found, "Test presence of positive int");
728 if ((fp = fopen("test2.cfg", "w")) == NULL) {
732 "# this is a comment\n"
733 "optprompt=\"Datastore Name\"\n"
735 "optprompt=\"New Hostname to create\"\n"
737 "optprompt=\"Some 64 integer\"\n"
745 "plugin.test=@STR@\n"
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");
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");
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");
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");
770 ok((pos = ini->get_item("bool")) == 4, "Check bool definition");
771 ok(ini->items[pos].val.boolval == true, "Test bool");
773 ok(ini->dump_results(&buf), "Test to dump results");
774 printf("<%s>\n", buf);
780 free_pool_memory(buf);