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)
544 /* TODO, write back the alist to edit buffer */
547 if (lex_get_token(lc, T_STRING) == T_ERROR) {
551 if (item->val.alistval == NULL) {
552 list = New(alist(10, owned_by_alist));
554 list = item->val.alistval;
557 Dmsg4(900, "Append %s to alist %p size=%d %s\n",
558 lc->str, list, list->size(), item->name);
559 list->append(bstrdup(lc->str));
560 item->val.alistval = list;
566 bool ini_store_pint64(LEX *lc, ConfigFile *inifile, ini_items *item)
569 Mmsg(inifile->edit, "%lld", item->val.int64val);
572 if (lex_get_token(lc, T_PINT64) == T_ERROR) {
575 item->val.int64val = lc->pint64_val;
580 bool ini_store_int64(LEX *lc, ConfigFile *inifile, ini_items *item)
583 Mmsg(inifile->edit, "%lld", item->val.int64val);
586 if (lex_get_token(lc, T_INT64) == T_ERROR) {
589 item->val.int64val = lc->int64_val;
594 bool ini_store_pint32(LEX *lc, ConfigFile *inifile, ini_items *item)
597 Mmsg(inifile->edit, "%d", item->val.int32val);
600 if (lex_get_token(lc, T_PINT32) == T_ERROR) {
603 item->val.int32val = lc->pint32_val;
608 bool ini_store_int32(LEX *lc, ConfigFile *inifile, ini_items *item)
611 Mmsg(inifile->edit, "%d", item->val.int32val);
614 if (lex_get_token(lc, T_INT32) == T_ERROR) {
617 item->val.int32val = lc->int32_val;
622 bool ini_store_bool(LEX *lc, ConfigFile *inifile, ini_items *item)
625 Mmsg(inifile->edit, "%s", item->val.boolval?"yes":"no");
628 if (lex_get_token(lc, T_NAME) == T_ERROR) {
631 if (strcasecmp(lc->str, "yes") == 0 ||
632 strcasecmp(lc->str, "true") == 0 ||
633 strcasecmp(lc->str, "on") == 0 ||
634 strcasecmp(lc->str, "1") == 0)
636 item->val.boolval = true;
638 } else if (strcasecmp(lc->str, "no") == 0 ||
639 strcasecmp(lc->str, "false") == 0 ||
640 strcasecmp(lc->str, "off") == 0 ||
641 strcasecmp(lc->str, "0") == 0)
643 item->val.boolval = false;
646 /* YES and NO must not be translated */
647 scan_err2(lc, _("Expect %s, got: %s"), "YES, NO, ON, OFF, 0, 1, TRUE, or FALSE", lc->str);
654 bool ini_store_date(LEX *lc, ConfigFile *inifile, ini_items *item)
657 bstrutime(inifile->edit,sizeof_pool_memory(inifile->edit),item->val.btimeval);
660 if (lex_get_token(lc, T_STRING) == T_ERROR) {
663 item->val.btimeval = str_to_utime(lc->str);
664 if (item->val.btimeval == 0) {
671 /* ---------------------------------------------------------------- */
674 * export LD_LIBRARY_PATH=.libs/
682 void _ok(const char *file, int l, const char *op, int value, const char *label)
687 printf("ERR %.45s %s:%i on %s\n", label, file, l, op);
689 printf("OK %.45s\n", label);
693 #define ok(x, label) _ok(__FILE__, __LINE__, #x, (x), label)
695 void _nok(const char *file, int l, const char *op, int value, const char *label)
700 printf("ERR %.45s %s:%i on !%s\n", label, file, l, op);
702 printf("OK %.45s\n", label);
706 #define nok(x, label) _nok(__FILE__, __LINE__, #x, (x), label)
710 printf("Result %i/%i OK\n", nb - err, nb);
714 struct ini_items membuf_items[] = {
715 /* name handler comment req */
716 {"client", ini_store_name, "Client name", 0},
717 {"serial", ini_store_int32, "Serial number", 1},
718 {"max_clients", ini_store_int32, "Max Clients", 0},
719 {NULL, NULL, NULL, 0}
723 struct ini_items test_items[] = {
724 /* name handler comment req */
725 {"datastore", ini_store_name, "Target Datastore", 0},
726 {"newhost", ini_store_str, "New Hostname", 1},
727 {"int64val", ini_store_int64, "Int64", 1},
728 {"list", ini_store_alist_str, "list", 0},
729 {"bool", ini_store_bool, "Bool", 0},
730 {"pint64", ini_store_pint64, "pint", 0},
731 {"int32", ini_store_int32, "int 32bit", 0},
732 {"plugin.test", ini_store_str, "test with .", 0},
733 {"adate", ini_store_date, "test with date", 0},
734 {NULL, NULL, NULL, 0}
737 /* In order to link with libbaccfg */
741 bool save_resource(RES_HEAD **rhead, int type, RES_ITEM *items, int pass){}
742 void dump_resource(int type, RES *ares, void sendit(void *sock, const char *fmt, ...), void *sock){}
743 void free_resource(RES *rres, int type){}
746 RES_TABLE resources[] = {};
753 ConfigFile *ini = new ConfigFile();
754 POOLMEM *buf = get_pool_memory(PM_BSOCK);
758 printf("Begin Memory buffer Test\n");
759 ok(ini->register_items(membuf_items, sizeof(struct ini_items)), "Check sizeof ini_items");
761 if ((fp = fopen("test.cfg", "w")) == NULL) {
764 fprintf(fp, "client=JohnDoe\n");
765 fprintf(fp, "serial=2\n");
766 fprintf(fp, "max_clients=3\n");
768 fseek(fp, 0, SEEK_END);
772 if ((fp = fopen("test.cfg", "rb")) == NULL) {
773 printf("Could not open file test.cfg\n");
777 size = fread(buffer, 1, size, fp);
779 //printf("size of read buffer is: %d\n", size);
780 //printf("====buf=%s\n", buffer);
782 ok(ini->parse_buf(buffer), "Test memory read with all members");
788 printf("\n\nBegin Original Full Tests\n");
789 nok(ini->register_items(test_items, 5), "Check bad sizeof ini_items");
790 ok(ini->register_items(test_items, sizeof(struct ini_items)), "Check sizeof ini_items");
792 if ((fp = fopen("test.cfg", "w")) == NULL) {
795 fprintf(fp, "# this is a comment\ndatastore=datastore1\nnewhost=\"host1\"\n");
798 nok(ini->parse("test.cfg"), "Test missing member");
801 fprintf(fp, "int64val=12 # with a comment\n");
802 fprintf(fp, "int64val=10 # with a comment\n");
803 fprintf(fp, "int32=100\n");
804 fprintf(fp, "bool=yes\n");
805 fprintf(fp, "plugin.test=parameter\n");
806 fprintf(fp, "adate=\"1970-01-02 12:00:00\"\n");
810 ok(ini->parse("test.cfg"), "Test with all members");
812 ok(ini->items[0].found, "Test presence of char[]");
813 ok(!strcmp(ini->items[0].val.nameval, "datastore1"), "Test char[]");
814 ok(ini->items[1].found, "Test presence of char*");
815 ok(!strcmp(ini->items[1].val.strval, "host1"), "Test char*");
816 ok(ini->items[2].found, "Test presence of int");
817 ok(ini->items[2].val.int64val == 10, "Test int");
818 ok(ini->items[4].val.boolval == true, "Test bool");
819 ok(ini->items[6].val.int32val == 100, "Test int 32");
820 ok(ini->items[6].val.btimeval != 126000, "Test btime");
822 alist *list = ini->items[3].val.alistval;
823 nok(ini->items[3].found, "Test presence of alist");
825 fprintf(fp, "list=a\nlist=b\nlist=c\n");
829 ok(ini->parse("test.cfg"), "Test with all members");
831 list = ini->items[3].val.alistval;
832 ok(ini->items[3].found, "Test presence of alist");
833 ok(list != NULL, "Test list member");
834 ok(list->size() == 3, "Test list size");
836 ok(!strcmp((char *)list->get(0), "a"), "Testing alist[0]");
837 ok(!strcmp((char *)list->get(1), "b"), "Testing alist[1]");
838 ok(!strcmp((char *)list->get(2), "c"), "Testing alist[2]");
840 system("cp -f test.cfg test3.cfg");
842 fprintf(fp, "pouet='10, 11, 12'\n");
843 fprintf(fp, "pint=-100\n");
844 fprintf(fp, "int64val=-100\n"); /* TODO: fix negative numbers */
848 ok(ini->parse("test.cfg"), "Test with errors");
849 nok(ini->items[5].found, "Test presence of positive int");
856 if ((fp = fopen("test2.cfg", "w")) == NULL) {
860 "# this is a comment\n"
861 "optprompt=\"Datastore Name\"\n"
863 "optprompt=\"New Hostname to create\"\n"
865 "optprompt=\"Some 64 integer\"\n"
873 "plugin.test=@STR@\n"
878 ok(ini->unserialize("test2.cfg"), "Test dynamic parse");
879 ok(ini->serialize("test4.cfg"), "Try to dump the item table in a file");
880 ok(ini->serialize(&buf) > 0, "Try to dump the item table in a buffer");
881 ok(ini->parse("test3.cfg"), "Parse test file with dynamic grammar");
883 ok((pos = ini->get_item("datastore")) == 0, "Check datastore definition");
884 ok(ini->items[pos].found, "Test presence of char[]");
885 ok(!strcmp(ini->items[pos].val.nameval, "datastore1"), "Test char[]");
886 ok(!strcmp(ini->items[pos].comment, "Datastore Name"), "Check comment");
887 ok(ini->items[pos].required == false, "Check required");
889 ok((pos = ini->get_item("newhost")) == 1, "Check newhost definition");
890 ok(ini->items[pos].found, "Test presence of char*");
891 ok(!strcmp(ini->items[pos].val.strval, "host1"), "Test char*");
892 ok(ini->items[pos].required == false, "Check required");
894 ok((pos = ini->get_item("int64val")) == 2, "Check int64val definition");
895 ok(ini->items[pos].found, "Test presence of int");
896 ok(ini->items[pos].val.int64val == 10, "Test int");
897 ok(ini->items[pos].required == true, "Check required");
899 ok((pos = ini->get_item("bool")) == 4, "Check bool definition");
900 ok(ini->items[pos].val.boolval == true, "Test bool");
902 ok((pos = ini->get_item("adate")) == 9, "Check adate definition");
903 ok(ini->items[pos].val.btimeval == 126000, "Test date");
905 ok(ini->dump_results(&buf), "Test to dump results");
906 printf("<%s>\n", buf);
912 free_pool_memory(buf);