2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2016 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 = fopen(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 = fopen(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 if (items[i].found) {
283 items[i].handler(NULL, this, &items[i]);
284 if (items[i].comment && *items[i].comment) {
285 Mmsg(tmp, "# %s\n", items[i].comment);
288 if (items[i].handler == ini_store_str ||
289 items[i].handler == ini_store_name ||
290 items[i].handler == ini_store_date)
292 Mmsg(tmp, "%s=%s\n\n",
294 quote_string(tmp2, this->edit));
297 Mmsg(tmp, "%s=%s\n\n", items[i].name, this->edit);
299 len = pm_strcat(buf, tmp);
302 free_pool_memory(tmp);
303 free_pool_memory(tmp2);
308 bool ConfigFile::parse()
314 lc->options |= LOPT_NO_EXTERN;
315 lc->caller_ctx = (void *)this;
317 while ((token=lex_get_token(lc, T_ALL)) != T_EOF) {
318 if (token == T_EOL) {
322 for (i=0; items[i].name; i++) {
323 if (strcasecmp(items[i].name, lc->str) == 0) {
324 if ((token = lex_get_token(lc, T_EQUALS)) == T_ERROR) {
325 Dmsg2(dbglevel, "in T_IDENT got token=%s str=%s\n", lex_tok_to_str(token), lc->str);
328 Dmsg2(dbglevel, "parse got token=%s str=%s\n", lex_tok_to_str(token), lc->str);
329 Dmsg1(dbglevel, "calling handler for %s\n", items[i].name);
330 /* Call item handler */
331 ret = items[i].found = items[i].handler(lc, this, &items[i]);
337 Dmsg1(dbglevel, "Unfound keyword=%s\n", lc->str);
338 scan_err1(lc, "Keyword %s not found", lc->str);
339 /* We can raise an error here */
342 Dmsg1(dbglevel, "Found keyword=%s\n", items[i].name);
345 Dmsg1(dbglevel, "Error getting value for keyword=%s\n", items[i].name);
348 Dmsg0(dbglevel, "Continue with while(token) loop\n");
351 for (i=0; items[i].name; i++) {
352 if (items[i].required && !items[i].found) {
353 scan_err1(lc, "%s required but not found", items[i].name);
358 lc = lex_close_file(lc);
362 /* Parse a config file used by Plugin/Director */
363 bool ConfigFile::parse(const char *fname)
369 if ((lc = lex_open_file(lc, fname, s_err)) == NULL) {
371 Emsg2(M_ERROR, 0, _("Cannot open config file %s: %s\n"),
372 fname, be.bstrerror());
375 return parse(); /* Parse file */
378 /* Parse a config buffer used by Plugin/Director */
379 bool ConfigFile::parse_buf(const char *buffer)
385 if ((lc = lex_open_buf(lc, buffer, s_err)) == NULL) {
386 Emsg0(M_ERROR, 0, _("Cannot open lex\n"));
389 return parse(); /* Parse memory buffer */
393 /* Analyse the content of a ini file to build the item list
394 * It uses special syntax for datatype. Used by Director on Restore object
396 * OptPrompt = "Variable1"
399 * Variable1 = @PINT32@
402 bool ConfigFile::unserialize(const char *fname)
404 int token, i, nb = 0;
408 /* At this time, we allow only 32 different items */
409 int s = MAX_INI_ITEMS * sizeof (struct ini_items);
411 items = (struct ini_items *) malloc (s);
413 items_allocated = true;
415 /* parse the file and generate the items structure on the fly */
416 if ((lc = lex_open_file(lc, fname, s_err)) == NULL) {
418 Emsg2(M_ERROR, 0, _("Cannot open config file %s: %s\n"),
419 fname, be.bstrerror());
422 lc->options |= LOPT_NO_EXTERN;
423 lc->caller_ctx = (void *)this;
425 while ((token=lex_get_token(lc, T_ALL)) != T_EOF) {
426 Dmsg1(dbglevel, "parse got token=%s\n", lex_tok_to_str(token));
428 if (token == T_EOL) {
435 if (nb >= MAX_INI_ITEMS) {
439 if (strcasecmp("optprompt", lc->str) == 0) {
440 assign = &(items[nb].comment);
442 } else if (strcasecmp("optdefault", lc->str) == 0) {
443 assign = &(items[nb].default_value);
445 } else if (strcasecmp("optrequired", lc->str) == 0) {
446 items[nb].required = true; /* Don't use argument */
451 items[nb].name = bstrdup(lc->str);
454 token = lex_get_token(lc, T_ALL);
455 Dmsg1(dbglevel, "in T_IDENT got token=%s\n", lex_tok_to_str(token));
457 if (token != T_EQUALS) {
458 scan_err1(lc, "expected an equals, got: %s", lc->str);
462 /* We may allow blank variable */
463 if (lex_get_token(lc, T_STRING) == T_ERROR) {
468 *assign = bstrdup(lc->str);
471 if ((items[nb].handler = ini_get_store_handler(lc->str)) == NULL) {
472 scan_err1(lc, "expected a data type, got: %s", lc->str);
482 for (i = 0; i < nb ; i++) {
483 bfree_and_null_const(items[i].name);
484 bfree_and_null_const(items[i].comment);
485 bfree_and_null_const(items[i].default_value);
486 items[i].handler = NULL;
487 items[i].required = false;
491 lc = lex_close_file(lc);
495 /* ----------------------------------------------------------------
496 * Handle data type. Import/Export
497 * ----------------------------------------------------------------
499 bool ini_store_str(LEX *lc, ConfigFile *inifile, ini_items *item)
502 Mmsg(inifile->edit, "%s", item->val.strval);
505 if (lex_get_token(lc, T_STRING) == T_ERROR) {
508 /* If already allocated, free first */
509 if (item->found && item->val.strval) {
510 free(item->val.strval);
512 item->val.strval = bstrdup(lc->str);
517 bool ini_store_name(LEX *lc, ConfigFile *inifile, ini_items *item)
520 Mmsg(inifile->edit, "%s", item->val.nameval);
523 if (lex_get_token(lc, T_NAME) == T_ERROR) {
524 Dmsg0(dbglevel, "Want token=T_NAME got T_ERROR\n");
527 Dmsg1(dbglevel, "ini_store_name: %s\n", lc->str);
528 strncpy(item->val.nameval, lc->str, sizeof(item->val.nameval));
533 bool ini_store_alist_str(LEX *lc, ConfigFile *inifile, ini_items *item)
537 /* TODO, write back the alist to edit buffer */
540 if (lex_get_token(lc, T_STRING) == T_ERROR) {
544 if (item->val.alistval == NULL) {
545 list = New(alist(10, owned_by_alist));
547 list = item->val.alistval;
550 Dmsg4(900, "Append %s to alist %p size=%d %s\n",
551 lc->str, list, list->size(), item->name);
552 list->append(bstrdup(lc->str));
553 item->val.alistval = list;
559 bool ini_store_pint64(LEX *lc, ConfigFile *inifile, ini_items *item)
562 Mmsg(inifile->edit, "%lld", item->val.int64val);
565 if (lex_get_token(lc, T_PINT64) == T_ERROR) {
568 item->val.int64val = lc->pint64_val;
573 bool ini_store_int64(LEX *lc, ConfigFile *inifile, ini_items *item)
576 Mmsg(inifile->edit, "%lld", item->val.int64val);
579 if (lex_get_token(lc, T_INT64) == T_ERROR) {
582 item->val.int64val = lc->int64_val;
587 bool ini_store_pint32(LEX *lc, ConfigFile *inifile, ini_items *item)
590 Mmsg(inifile->edit, "%d", item->val.int32val);
593 if (lex_get_token(lc, T_PINT32) == T_ERROR) {
596 item->val.int32val = lc->pint32_val;
601 bool ini_store_int32(LEX *lc, ConfigFile *inifile, ini_items *item)
604 Mmsg(inifile->edit, "%d", item->val.int32val);
607 if (lex_get_token(lc, T_INT32) == T_ERROR) {
610 item->val.int32val = lc->int32_val;
615 bool ini_store_bool(LEX *lc, ConfigFile *inifile, ini_items *item)
618 Mmsg(inifile->edit, "%s", item->val.boolval?"yes":"no");
621 if (lex_get_token(lc, T_NAME) == T_ERROR) {
624 if (strcasecmp(lc->str, "yes") == 0 ||
625 strcasecmp(lc->str, "true") == 0 ||
626 strcasecmp(lc->str, "on") == 0 ||
627 strcasecmp(lc->str, "1") == 0)
629 item->val.boolval = true;
631 } else if (strcasecmp(lc->str, "no") == 0 ||
632 strcasecmp(lc->str, "false") == 0 ||
633 strcasecmp(lc->str, "off") == 0 ||
634 strcasecmp(lc->str, "0") == 0)
636 item->val.boolval = false;
639 /* YES and NO must not be translated */
640 scan_err2(lc, _("Expect %s, got: %s"), "YES, NO, ON, OFF, 0, 1, TRUE, or FALSE", lc->str);
647 bool ini_store_date(LEX *lc, ConfigFile *inifile, ini_items *item)
650 bstrutime(inifile->edit,sizeof_pool_memory(inifile->edit),item->val.btimeval);
653 if (lex_get_token(lc, T_STRING) == T_ERROR) {
656 item->val.btimeval = str_to_utime(lc->str);
657 if (item->val.btimeval == 0) {
664 /* ---------------------------------------------------------------- */
667 * export LD_LIBRARY_PATH=.libs/
675 void _ok(const char *file, int l, const char *op, int value, const char *label)
680 printf("ERR %.45s %s:%i on %s\n", label, file, l, op);
682 printf("OK %.45s\n", label);
686 #define ok(x, label) _ok(__FILE__, __LINE__, #x, (x), label)
688 void _nok(const char *file, int l, const char *op, int value, const char *label)
693 printf("ERR %.45s %s:%i on !%s\n", label, file, l, op);
695 printf("OK %.45s\n", label);
699 #define nok(x, label) _nok(__FILE__, __LINE__, #x, (x), label)
703 printf("Result %i/%i OK\n", nb - err, nb);
707 struct ini_items membuf_items[] = {
708 /* name handler comment req */
709 {"client", ini_store_name, "Client name", 0},
710 {"serial", ini_store_int32, "Serial number", 1},
711 {"max_clients", ini_store_int32, "Max Clients", 0},
712 {NULL, NULL, NULL, 0}
716 struct ini_items test_items[] = {
717 /* name handler comment req */
718 {"datastore", ini_store_name, "Target Datastore", 0},
719 {"newhost", ini_store_str, "New Hostname", 1},
720 {"int64val", ini_store_int64, "Int64", 1},
721 {"list", ini_store_alist_str, "list", 0},
722 {"bool", ini_store_bool, "Bool", 0},
723 {"pint64", ini_store_pint64, "pint", 0},
724 {"int32", ini_store_int32, "int 32bit", 0},
725 {"plugin.test", ini_store_str, "test with .", 0},
726 {"adate", ini_store_date, "test with date", 0},
727 {NULL, NULL, NULL, 0}
730 /* In order to link with libbaccfg */
734 void save_resource(int type, RES_ITEM *items, int pass){}
735 void dump_resource(int type, RES *ares, void sendit(void *sock, const char *fmt, ...), void *sock){}
736 void free_resource(RES *rres, int type){}
739 RES_TABLE resources[] = {};
746 ConfigFile *ini = new ConfigFile();
747 POOLMEM *buf = get_pool_memory(PM_BSOCK);
751 printf("Begin Memory buffer Test\n");
752 ok(ini->register_items(membuf_items, sizeof(struct ini_items)), "Check sizeof ini_items");
754 if ((fp = fopen("test.cfg", "w")) == NULL) {
757 fprintf(fp, "client=JohnDoe\n");
758 fprintf(fp, "serial=2\n");
759 fprintf(fp, "max_clients=3\n");
761 fseek(fp, 0, SEEK_END);
765 if ((fp = fopen("test.cfg", "rb")) == NULL) {
766 printf("Could not open file test.cfg\n");
770 size = fread(buffer, 1, size, fp);
772 //printf("size of read buffer is: %d\n", size);
773 //printf("====buf=%s\n", buffer);
775 ok(ini->parse_buf(buffer), "Test memory read with all members");
781 printf("\n\nBegin Original Full Tests\n");
782 nok(ini->register_items(test_items, 5), "Check bad sizeof ini_items");
783 ok(ini->register_items(test_items, sizeof(struct ini_items)), "Check sizeof ini_items");
785 if ((fp = fopen("test.cfg", "w")) == NULL) {
788 fprintf(fp, "# this is a comment\ndatastore=datastore1\nnewhost=\"host1\"\n");
791 nok(ini->parse("test.cfg"), "Test missing member");
794 fprintf(fp, "int64val=12 # with a comment\n");
795 fprintf(fp, "int64val=10 # with a comment\n");
796 fprintf(fp, "int32=100\n");
797 fprintf(fp, "bool=yes\n");
798 fprintf(fp, "plugin.test=parameter\n");
799 fprintf(fp, "adate=\"1970-01-02 12:00:00\"\n");
803 ok(ini->parse("test.cfg"), "Test with all members");
805 ok(ini->items[0].found, "Test presence of char[]");
806 ok(!strcmp(ini->items[0].val.nameval, "datastore1"), "Test char[]");
807 ok(ini->items[1].found, "Test presence of char*");
808 ok(!strcmp(ini->items[1].val.strval, "host1"), "Test char*");
809 ok(ini->items[2].found, "Test presence of int");
810 ok(ini->items[2].val.int64val == 10, "Test int");
811 ok(ini->items[4].val.boolval == true, "Test bool");
812 ok(ini->items[6].val.int32val == 100, "Test int 32");
813 ok(ini->items[6].val.btimeval != 126000, "Test btime");
815 alist *list = ini->items[3].val.alistval;
816 nok(ini->items[3].found, "Test presence of alist");
818 fprintf(fp, "list=a\nlist=b\nlist=c\n");
822 ok(ini->parse("test.cfg"), "Test with all members");
824 list = ini->items[3].val.alistval;
825 ok(ini->items[3].found, "Test presence of alist");
826 ok(list != NULL, "Test list member");
827 ok(list->size() == 3, "Test list size");
829 ok(!strcmp((char *)list->get(0), "a"), "Testing alist[0]");
830 ok(!strcmp((char *)list->get(1), "b"), "Testing alist[1]");
831 ok(!strcmp((char *)list->get(2), "c"), "Testing alist[2]");
833 system("cp -f test.cfg test3.cfg");
835 fprintf(fp, "pouet='10, 11, 12'\n");
836 fprintf(fp, "pint=-100\n");
837 fprintf(fp, "int64val=-100\n"); /* TODO: fix negative numbers */
841 ok(ini->parse("test.cfg"), "Test with errors");
842 nok(ini->items[5].found, "Test presence of positive int");
849 if ((fp = fopen("test2.cfg", "w")) == NULL) {
853 "# this is a comment\n"
854 "optprompt=\"Datastore Name\"\n"
856 "optprompt=\"New Hostname to create\"\n"
858 "optprompt=\"Some 64 integer\"\n"
866 "plugin.test=@STR@\n"
871 ok(ini->unserialize("test2.cfg"), "Test dynamic parse");
872 ok(ini->serialize("test4.cfg"), "Try to dump the item table in a file");
873 ok(ini->serialize(&buf) > 0, "Try to dump the item table in a buffer");
874 ok(ini->parse("test3.cfg"), "Parse test file with dynamic grammar");
876 ok((pos = ini->get_item("datastore")) == 0, "Check datastore definition");
877 ok(ini->items[pos].found, "Test presence of char[]");
878 ok(!strcmp(ini->items[pos].val.nameval, "datastore1"), "Test char[]");
879 ok(!strcmp(ini->items[pos].comment, "Datastore Name"), "Check comment");
880 ok(ini->items[pos].required == false, "Check required");
882 ok((pos = ini->get_item("newhost")) == 1, "Check newhost definition");
883 ok(ini->items[pos].found, "Test presence of char*");
884 ok(!strcmp(ini->items[pos].val.strval, "host1"), "Test char*");
885 ok(ini->items[pos].required == false, "Check required");
887 ok((pos = ini->get_item("int64val")) == 2, "Check int64val definition");
888 ok(ini->items[pos].found, "Test presence of int");
889 ok(ini->items[pos].val.int64val == 10, "Test int");
890 ok(ini->items[pos].required == true, "Check required");
892 ok((pos = ini->get_item("bool")) == 4, "Check bool definition");
893 ok(ini->items[pos].val.boolval == true, "Test bool");
895 ok((pos = ini->get_item("adate")) == 9, "Check adate definition");
896 ok(ini->items[pos].val.btimeval == 126000, "Test date");
898 ok(ini->dump_results(&buf), "Test to dump results");
899 printf("<%s>\n", buf);
905 free_pool_memory(buf);