2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2017 Kern Sibbald
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.
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.
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
20 * Handle simple configuration file such as "ini" files.
21 * key1 = val # comment
29 #define bfree_and_null_const(a) do{if(a){free((void *)a); (a)=NULL;}} while(0)
30 static int dbglevel = 100;
32 /* We use this structure to associate a key to the function */
36 INI_ITEM_HANDLER *handler;
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@ */
54 * Get handler code from handler @
56 const char *ini_get_store_code(INI_ITEM_HANDLER *handler)
58 for (int i = 0; funcs[i].key ; i++) {
59 if (funcs[i].handler == handler) {
67 * Get handler function from handler name
69 INI_ITEM_HANDLER *ini_get_store_handler(const char *key)
71 for (int i = 0; funcs[i].key ; i++) {
72 if (!strcmp(funcs[i].key, key)) {
73 return funcs[i].handler;
80 * Format a scanner error message
82 static void s_err(const char *file, int line, LEX *lc, const char *msg, ...)
84 ConfigFile *ini = (ConfigFile *)(lc->caller_ctx);
88 va_start(arg_ptr, msg);
89 bvsnprintf(buf, sizeof(buf), msg, arg_ptr);
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);
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);
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);
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);
117 /* Reset free items */
118 void ConfigFile::clear_items()
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;
131 } else if (items[i].handler == ini_store_alist_str) {
132 delete items[i].val.alistval;
133 items[i].val.alistval = NULL;
135 items[i].found = false;
140 void ConfigFile::free_items()
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);
153 items_allocated = false;
156 /* Get a particular item from the items list */
157 int ConfigFile::get_item(const char *name)
163 for (int i=0; i < MAX_INI_ITEMS && items[i].name; i++) {
164 if (strcasecmp(name, items[i].name) == 0) {
171 /* Dump a buffer to a file in the working directory
172 * Needed to unserialise() a config
174 bool ConfigFile::dump_string(const char *buf, int32_t len)
180 out_fname = get_pool_memory(PM_FNAME);
181 make_unique_filename(&out_fname, (int)(intptr_t)this, (char*)"configfile");
184 fp = bfopen(out_fname, "wb");
189 if (fwrite(buf, len, 1, fp) == 1) {
197 /* Dump the item table format to a text file (used by plugin) */
198 bool ConfigFile::serialize(const char *fname)
209 fp = bfopen(fname, "w");
214 tmp = get_pool_memory(PM_MESSAGE);
215 len = serialize(&tmp);
216 if (fwrite(tmp, len, 1, fp) == 1) {
219 free_pool_memory(tmp);
225 /* Dump the item table format to a text file (used by plugin) */
226 int ConfigFile::serialize(POOLMEM **buf)
235 len = Mmsg(buf, "# Plugin configuration file\n# Version %d\n", version);
237 tmp = get_pool_memory(PM_MESSAGE);
238 tmp2 = get_pool_memory(PM_MESSAGE);
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));
245 if (items[i].default_value) {
246 Mmsg(tmp, "OptDefault=%s\n",
247 quote_string(tmp2, items[i].default_value));
250 if (items[i].required) {
251 Mmsg(tmp, "OptRequired=yes\n");
255 Mmsg(tmp, "%s=%s\n\n",
256 items[i].name, ini_get_store_code(items[i].handler));
258 /* variable = @INT64@ */
259 len = pm_strcat(buf, tmp);
261 free_pool_memory(tmp);
262 free_pool_memory(tmp2);
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);
279 tmp2 = get_pool_memory(PM_MESSAGE);
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]);
286 if (!items[i].found && items[i].required && items[i].default_value) {
287 pm_strcpy(this->edit, items[i].default_value);
291 if (items[i].comment && *items[i].comment) {
292 Mmsg(tmp, "# %s\n", items[i].comment);
295 if (items[i].handler == ini_store_str ||
296 items[i].handler == ini_store_name ||
297 items[i].handler == ini_store_date)
299 Mmsg(tmp, "%s=%s\n\n",
301 quote_string(tmp2, this->edit));
304 Mmsg(tmp, "%s=%s\n\n", items[i].name, this->edit);
306 len = pm_strcat(buf, tmp);
309 free_pool_memory(tmp);
310 free_pool_memory(tmp2);
315 bool ConfigFile::parse()
321 lc->options |= LOPT_NO_EXTERN;
322 lc->caller_ctx = (void *)this;
324 while ((token=lex_get_token(lc, T_ALL)) != T_EOF) {
325 if (token == T_EOL) {
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);
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]);
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 */
349 Dmsg1(dbglevel, "Found keyword=%s\n", items[i].name);
352 Dmsg1(dbglevel, "Error getting value for keyword=%s\n", items[i].name);
355 Dmsg0(dbglevel, "Continue with while(token) loop\n");
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);
365 lc = lex_close_file(lc);
369 /* Parse a config file used by Plugin/Director */
370 bool ConfigFile::parse(const char *fname)
376 if ((lc = lex_open_file(lc, fname, s_err)) == NULL) {
378 Emsg2(M_ERROR, 0, _("Cannot open config file %s: %s\n"),
379 fname, be.bstrerror());
382 return parse(); /* Parse file */
385 /* Parse a config buffer used by Plugin/Director */
386 bool ConfigFile::parse_buf(const char *buffer)
392 if ((lc = lex_open_buf(lc, buffer, s_err)) == NULL) {
393 Emsg0(M_ERROR, 0, _("Cannot open lex\n"));
396 return parse(); /* Parse memory buffer */
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
403 * OptPrompt = "Variable1"
406 * Variable1 = @PINT32@
409 bool ConfigFile::unserialize(const char *fname)
411 int token, i, nb = 0;
415 /* At this time, we allow only 32 different items */
416 int s = MAX_INI_ITEMS * sizeof (struct ini_items);
418 items = (struct ini_items *) malloc (s);
420 items_allocated = true;
422 /* parse the file and generate the items structure on the fly */
423 if ((lc = lex_open_file(lc, fname, s_err)) == NULL) {
425 Emsg2(M_ERROR, 0, _("Cannot open config file %s: %s\n"),
426 fname, be.bstrerror());
429 lc->options |= LOPT_NO_EXTERN;
430 lc->caller_ctx = (void *)this;
432 while ((token=lex_get_token(lc, T_ALL)) != T_EOF) {
433 Dmsg1(dbglevel, "parse got token=%s\n", lex_tok_to_str(token));
435 if (token == T_EOL) {
442 if (nb >= MAX_INI_ITEMS) {
446 if (strcasecmp("optprompt", lc->str) == 0) {
447 assign = &(items[nb].comment);
449 } else if (strcasecmp("optdefault", lc->str) == 0) {
450 assign = &(items[nb].default_value);
452 } else if (strcasecmp("optrequired", lc->str) == 0) {
453 items[nb].required = true; /* Don't use argument */
458 items[nb].name = bstrdup(lc->str);
461 token = lex_get_token(lc, T_ALL);
462 Dmsg1(dbglevel, "in T_IDENT got token=%s\n", lex_tok_to_str(token));
464 if (token != T_EQUALS) {
465 scan_err1(lc, "expected an equals, got: %s", lc->str);
469 /* We may allow blank variable */
470 if (lex_get_token(lc, T_STRING) == T_ERROR) {
475 *assign = bstrdup(lc->str);
478 if ((items[nb].handler = ini_get_store_handler(lc->str)) == NULL) {
479 scan_err1(lc, "expected a data type, got: %s", lc->str);
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;
498 lc = lex_close_file(lc);
502 /* ----------------------------------------------------------------
503 * Handle data type. Import/Export
504 * ----------------------------------------------------------------
506 bool ini_store_str(LEX *lc, ConfigFile *inifile, ini_items *item)
509 Mmsg(inifile->edit, "%s", item->val.strval);
512 if (lex_get_token(lc, T_STRING) == T_ERROR) {
515 /* If already allocated, free first */
516 if (item->found && item->val.strval) {
517 free(item->val.strval);
519 item->val.strval = bstrdup(lc->str);
524 bool ini_store_name(LEX *lc, ConfigFile *inifile, ini_items *item)
527 Mmsg(inifile->edit, "%s", item->val.nameval);
530 if (lex_get_token(lc, T_NAME) == T_ERROR) {
531 Dmsg0(dbglevel, "Want token=T_NAME got T_ERROR\n");
534 Dmsg1(dbglevel, "ini_store_name: %s\n", lc->str);
535 strncpy(item->val.nameval, lc->str, sizeof(item->val.nameval));
540 bool ini_store_alist_str(LEX *lc, ConfigFile *inifile, ini_items *item)
542 alist *list = item->val.alistval;
544 /* TODO, write back the alist to edit buffer */
549 if (lex_get_token(lc, T_STRING) == T_ERROR) {
554 list = New(alist(10, owned_by_alist));
556 list->append(bstrdup(lc->str));
558 if (lc->ch != ',') { /* if no other item follows */
559 if (!lex_check_eol(lc)) {
560 /* found garbage at the end of the line */
565 lex_get_token(lc, T_ALL); /* eat comma */
568 item->val.alistval = list;
573 bool ini_store_pint64(LEX *lc, ConfigFile *inifile, ini_items *item)
576 Mmsg(inifile->edit, "%lld", item->val.int64val);
579 if (lex_get_token(lc, T_PINT64) == T_ERROR) {
582 item->val.int64val = lc->pint64_val;
587 bool ini_store_int64(LEX *lc, ConfigFile *inifile, ini_items *item)
590 Mmsg(inifile->edit, "%lld", item->val.int64val);
593 if (lex_get_token(lc, T_INT64) == T_ERROR) {
596 item->val.int64val = lc->int64_val;
601 bool ini_store_pint32(LEX *lc, ConfigFile *inifile, ini_items *item)
604 Mmsg(inifile->edit, "%d", item->val.int32val);
607 if (lex_get_token(lc, T_PINT32) == T_ERROR) {
610 item->val.int32val = lc->pint32_val;
615 bool ini_store_int32(LEX *lc, ConfigFile *inifile, ini_items *item)
618 Mmsg(inifile->edit, "%d", item->val.int32val);
621 if (lex_get_token(lc, T_INT32) == T_ERROR) {
624 item->val.int32val = lc->int32_val;
629 bool ini_store_bool(LEX *lc, ConfigFile *inifile, ini_items *item)
632 Mmsg(inifile->edit, "%s", item->val.boolval?"yes":"no");
635 if (lex_get_token(lc, T_NAME) == T_ERROR) {
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)
643 item->val.boolval = true;
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)
650 item->val.boolval = false;
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);
661 bool ini_store_date(LEX *lc, ConfigFile *inifile, ini_items *item)
664 bstrutime(inifile->edit,sizeof_pool_memory(inifile->edit),item->val.btimeval);
667 if (lex_get_token(lc, T_STRING) == T_ERROR) {
670 item->val.btimeval = str_to_utime(lc->str);
671 if (item->val.btimeval == 0) {
678 /* ---------------------------------------------------------------- */
681 * export LD_LIBRARY_PATH=.libs/
689 void _ok(const char *file, int l, const char *op, int value, const char *label)
694 printf("ERR %.45s %s:%i on %s\n", label, file, l, op);
696 printf("OK %.45s\n", label);
700 #define ok(x, label) _ok(__FILE__, __LINE__, #x, (x), label)
702 void _nok(const char *file, int l, const char *op, int value, const char *label)
707 printf("ERR %.45s %s:%i on !%s\n", label, file, l, op);
709 printf("OK %.45s\n", label);
713 #define nok(x, label) _nok(__FILE__, __LINE__, #x, (x), label)
717 printf("Result %i/%i OK\n", nb - err, nb);
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}
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}
744 /* In order to link with libbaccfg */
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){}
754 RES_TABLE resources[] = {};
761 ConfigFile *ini = new ConfigFile();
762 POOLMEM *buf = get_pool_memory(PM_BSOCK);
766 printf("Begin Memory buffer Test\n");
767 ok(ini->register_items(membuf_items, sizeof(struct ini_items)), "Check sizeof ini_items");
769 if ((fp = fopen("test.cfg", "w")) == NULL) {
772 fprintf(fp, "client=JohnDoe\n");
773 fprintf(fp, "serial=2\n");
774 fprintf(fp, "max_clients=3\n");
776 fseek(fp, 0, SEEK_END);
780 if ((fp = fopen("test.cfg", "rb")) == NULL) {
781 printf("Could not open file test.cfg\n");
785 size = fread(buffer, 1, size, fp);
787 //printf("size of read buffer is: %d\n", size);
788 //printf("====buf=%s\n", buffer);
790 ok(ini->parse_buf(buffer), "Test memory read with all members");
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");
800 if ((fp = fopen("test.cfg", "w")) == NULL) {
803 fprintf(fp, "# this is a comment\ndatastore=datastore1\nnewhost=\"host1\"\n");
806 nok(ini->parse("test.cfg"), "Test missing member");
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");
818 ok(ini->parse("test.cfg"), "Test with all members");
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");
830 alist *list = ini->items[3].val.alistval;
831 nok(ini->items[3].found, "Test presence of alist");
833 fprintf(fp, "list=a\nlist=b\nlist=c,d,e\n");
837 ok(ini->parse("test.cfg"), "Test with all members");
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");
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]");
848 system("cp -f test.cfg test3.cfg");
850 fprintf(fp, "pouet='10, 11, 12'\n");
851 fprintf(fp, "pint=-100\n");
852 fprintf(fp, "int64val=-100\n"); /* TODO: fix negative numbers */
856 ok(ini->parse("test.cfg"), "Test with errors");
857 nok(ini->items[5].found, "Test presence of positive int");
864 if ((fp = fopen("test2.cfg", "w")) == NULL) {
868 "# this is a comment\n"
869 "optprompt=\"Datastore Name\"\n"
871 "optprompt=\"New Hostname to create\"\n"
873 "optprompt=\"Some 64 integer\"\n"
881 "plugin.test=@STR@\n"
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");
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");
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");
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");
907 ok((pos = ini->get_item("bool")) == 4, "Check bool definition");
908 ok(ini->items[pos].val.boolval == true, "Test bool");
910 ok((pos = ini->get_item("adate")) == 9, "Check adate definition");
911 ok(ini->items[pos].val.btimeval == 126000, "Test date");
913 ok(ini->dump_results(&buf), "Test to dump results");
914 printf("<%s>\n", buf);
920 free_pool_memory(buf);