2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2015 Kern Sibbald
5 Copyright (C) 2010-2014 Free Software Foundation Europe e.V.
7 The original author of Bacula is Kern Sibbald, with contributions
8 from many others, a complete list can be found in the file AUTHORS.
10 You may use this file and others of this release according to the
11 license defined in the LICENSE file, which includes the Affero General
12 Public License, v3.0 ("AGPLv3") and some additional permissions and
13 terms pursuant to its AGPLv3 Section 7.
15 This notice must be preserved when any source code is
16 conveyed and/or propagated.
18 Bacula(R) is a registered trademark of Kern Sibbald.
21 * Handle simple configuration file such as "ini" files.
22 * key1 = val # comment
30 #define bfree_and_null_const(a) do{if(a){free((void *)a); (a)=NULL;}} while(0)
31 static int dbglevel = 100;
33 /* We use this structure to associate a key to the function */
37 INI_ITEM_HANDLER *handler;
40 static struct ini_store funcs[] = {
41 {"@INT32@", "Integer", ini_store_int32},
42 {"@PINT32@", "Integer", ini_store_pint32},
43 {"@PINT64@", "Positive Integer", ini_store_pint64},
44 {"@INT64@", "Integer", ini_store_int64},
45 {"@NAME@", "Simple String", ini_store_name},
46 {"@STR@", "String", ini_store_str},
47 {"@BOOL@", "on/off", ini_store_bool},
48 {"@ALIST@", "String list", ini_store_alist_str},
49 {"@DATE@", "Date", ini_store_date},
50 /* TODO: Add protocol for the FD @ASKFD@ */
55 * Get handler code from handler @
57 const char *ini_get_store_code(INI_ITEM_HANDLER *handler)
59 for (int i = 0; funcs[i].key ; i++) {
60 if (funcs[i].handler == handler) {
68 * Get handler function from handler name
70 INI_ITEM_HANDLER *ini_get_store_handler(const char *key)
72 for (int i = 0; funcs[i].key ; i++) {
73 if (!strcmp(funcs[i].key, key)) {
74 return funcs[i].handler;
81 * Format a scanner error message
83 static void s_err(const char *file, int line, LEX *lc, const char *msg, ...)
85 ConfigFile *ini = (ConfigFile *)(lc->caller_ctx);
89 va_start(arg_ptr, msg);
90 bvsnprintf(buf, sizeof(buf), msg, arg_ptr);
94 printf("ERROR: Config file error: %s\n"
95 " : Line %d, col %d of file %s\n%s\n",
96 buf, lc->line_no, lc->col_no, lc->fname, lc->line);
99 if (ini->jcr) { /* called from core */
100 Jmsg(ini->jcr, M_ERROR, 0, _("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);
104 // } else if (ini->ctx) { /* called from plugin */
105 // ini->bfuncs->JobMessage(ini->ctx, __FILE__, __LINE__, M_FATAL, 0,
106 // _("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 { /* called from ??? */
111 e_msg(file, line, M_ERROR, 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);
118 /* Reset free items */
119 void ConfigFile::clear_items()
125 for (int i=0; items[i].name; i++) {
126 if (items[i].found) {
127 /* special members require delete or free */
128 if (items[i].handler == ini_store_str) {
129 free(items[i].val.strval);
130 items[i].val.strval = NULL;
132 } else if (items[i].handler == ini_store_alist_str) {
133 delete items[i].val.alistval;
134 items[i].val.alistval = NULL;
136 items[i].found = false;
141 void ConfigFile::free_items()
143 if (items_allocated) {
144 for (int i=0; items[i].name; i++) {
145 bfree_and_null_const(items[i].name);
146 bfree_and_null_const(items[i].comment);
147 bfree_and_null_const(items[i].default_value);
154 items_allocated = false;
157 /* Get a particular item from the items list */
158 int ConfigFile::get_item(const char *name)
164 for (int i=0; i < MAX_INI_ITEMS && items[i].name; i++) {
165 if (strcasecmp(name, items[i].name) == 0) {
172 /* Dump a buffer to a file in the working directory
173 * Needed to unserialise() a config
175 bool ConfigFile::dump_string(const char *buf, int32_t len)
181 out_fname = get_pool_memory(PM_FNAME);
182 make_unique_filename(&out_fname, (int)(intptr_t)this, (char*)"configfile");
185 fp = fopen(out_fname, "wb");
190 if (fwrite(buf, len, 1, fp) == 1) {
198 /* Dump the item table format to a text file (used by plugin) */
199 bool ConfigFile::serialize(const char *fname)
210 fp = fopen(fname, "w");
215 tmp = get_pool_memory(PM_MESSAGE);
216 len = serialize(&tmp);
217 if (fwrite(tmp, len, 1, fp) == 1) {
220 free_pool_memory(tmp);
226 /* Dump the item table format to a text file (used by plugin) */
227 int ConfigFile::serialize(POOLMEM **buf)
236 len = Mmsg(buf, "# Plugin configuration file\n# Version %d\n", version);
238 tmp = get_pool_memory(PM_MESSAGE);
239 tmp2 = get_pool_memory(PM_MESSAGE);
241 for (int i=0; items[i].name ; i++) {
242 if (items[i].comment) {
243 Mmsg(tmp, "OptPrompt=%s\n", quote_string(tmp2, items[i].comment));
246 if (items[i].default_value) {
247 Mmsg(tmp, "OptDefault=%s\n",
248 quote_string(tmp2, items[i].default_value));
251 if (items[i].required) {
252 Mmsg(tmp, "OptRequired=yes\n");
256 Mmsg(tmp, "%s=%s\n\n",
257 items[i].name, ini_get_store_code(items[i].handler));
259 /* variable = @INT64@ */
260 len = pm_strcat(buf, tmp);
262 free_pool_memory(tmp);
263 free_pool_memory(tmp2);
268 /* Dump the item table content to a text file (used by director) */
269 int ConfigFile::dump_results(POOLMEM **buf)
277 len = Mmsg(buf, "# Plugin configuration file\n# Version %d\n", version);
279 tmp = get_pool_memory(PM_MESSAGE);
280 tmp2 = get_pool_memory(PM_MESSAGE);
282 for (int i=0; items[i].name ; i++) {
283 if (items[i].found) {
284 items[i].handler(NULL, this, &items[i]);
285 if (items[i].comment && *items[i].comment) {
286 Mmsg(tmp, "# %s\n", items[i].comment);
289 if (items[i].handler == ini_store_str ||
290 items[i].handler == ini_store_name ||
291 items[i].handler == ini_store_date)
293 Mmsg(tmp, "%s=%s\n\n",
295 quote_string(tmp2, this->edit));
298 Mmsg(tmp, "%s=%s\n\n", items[i].name, this->edit);
300 len = pm_strcat(buf, tmp);
303 free_pool_memory(tmp);
304 free_pool_memory(tmp2);
309 bool ConfigFile::parse()
315 lc->options |= LOPT_NO_EXTERN;
316 lc->caller_ctx = (void *)this;
318 while ((token=lex_get_token(lc, T_ALL)) != T_EOF) {
319 if (token == T_EOL) {
323 for (i=0; items[i].name; i++) {
324 if (strcasecmp(items[i].name, lc->str) == 0) {
325 if ((token = lex_get_token(lc, T_EQUALS)) == T_ERROR) {
326 Dmsg2(dbglevel, "in T_IDENT got token=%s str=%s\n", lex_tok_to_str(token), lc->str);
329 Dmsg2(dbglevel, "parse got token=%s str=%s\n", lex_tok_to_str(token), lc->str);
330 Dmsg1(dbglevel, "calling handler for %s\n", items[i].name);
331 /* Call item handler */
332 ret = items[i].found = items[i].handler(lc, this, &items[i]);
338 Dmsg1(dbglevel, "Unfound keyword=%s\n", lc->str);
339 scan_err1(lc, "Keyword %s not found", lc->str);
340 /* We can raise an error here */
343 Dmsg1(dbglevel, "Found keyword=%s\n", items[i].name);
346 Dmsg1(dbglevel, "Error getting value for keyword=%s\n", items[i].name);
349 Dmsg0(dbglevel, "Continue with while(token) loop\n");
352 for (i=0; items[i].name; i++) {
353 if (items[i].required && !items[i].found) {
354 scan_err1(lc, "%s required but not found", items[i].name);
359 lc = lex_close_file(lc);
363 /* Parse a config file used by Plugin/Director */
364 bool ConfigFile::parse(const char *fname)
370 if ((lc = lex_open_file(lc, fname, s_err)) == NULL) {
372 Emsg2(M_ERROR, 0, _("Cannot open config file %s: %s\n"),
373 fname, be.bstrerror());
376 return parse(); /* Parse file */
379 /* Parse a config buffer used by Plugin/Director */
380 bool ConfigFile::parse_buf(const char *buffer)
386 if ((lc = lex_open_buf(lc, buffer, s_err)) == NULL) {
387 Emsg0(M_ERROR, 0, _("Cannot open lex\n"));
390 return parse(); /* Parse memory buffer */
394 /* Analyse the content of a ini file to build the item list
395 * It uses special syntax for datatype. Used by Director on Restore object
397 * OptPrompt = "Variable1"
400 * Variable1 = @PINT32@
403 bool ConfigFile::unserialize(const char *fname)
405 int token, i, nb = 0;
409 /* At this time, we allow only 32 different items */
410 int s = MAX_INI_ITEMS * sizeof (struct ini_items);
412 items = (struct ini_items *) malloc (s);
414 items_allocated = true;
416 /* parse the file and generate the items structure on the fly */
417 if ((lc = lex_open_file(lc, fname, s_err)) == NULL) {
419 Emsg2(M_ERROR, 0, _("Cannot open config file %s: %s\n"),
420 fname, be.bstrerror());
423 lc->options |= LOPT_NO_EXTERN;
424 lc->caller_ctx = (void *)this;
426 while ((token=lex_get_token(lc, T_ALL)) != T_EOF) {
427 Dmsg1(dbglevel, "parse got token=%s\n", lex_tok_to_str(token));
429 if (token == T_EOL) {
436 if (nb >= MAX_INI_ITEMS) {
440 if (strcasecmp("optprompt", lc->str) == 0) {
441 assign = &(items[nb].comment);
443 } else if (strcasecmp("optdefault", lc->str) == 0) {
444 assign = &(items[nb].default_value);
446 } else if (strcasecmp("optrequired", lc->str) == 0) {
447 items[nb].required = true; /* Don't use argument */
452 items[nb].name = bstrdup(lc->str);
455 token = lex_get_token(lc, T_ALL);
456 Dmsg1(dbglevel, "in T_IDENT got token=%s\n", lex_tok_to_str(token));
458 if (token != T_EQUALS) {
459 scan_err1(lc, "expected an equals, got: %s", lc->str);
463 /* We may allow blank variable */
464 if (lex_get_token(lc, T_STRING) == T_ERROR) {
469 *assign = bstrdup(lc->str);
472 if ((items[nb].handler = ini_get_store_handler(lc->str)) == NULL) {
473 scan_err1(lc, "expected a data type, got: %s", lc->str);
483 for (i = 0; i < nb ; i++) {
484 bfree_and_null_const(items[i].name);
485 bfree_and_null_const(items[i].comment);
486 bfree_and_null_const(items[i].default_value);
487 items[i].handler = NULL;
488 items[i].required = false;
492 lc = lex_close_file(lc);
496 /* ----------------------------------------------------------------
497 * Handle data type. Import/Export
498 * ----------------------------------------------------------------
500 bool ini_store_str(LEX *lc, ConfigFile *inifile, ini_items *item)
503 Mmsg(inifile->edit, "%s", item->val.strval);
506 if (lex_get_token(lc, T_STRING) == T_ERROR) {
509 /* If already allocated, free first */
510 if (item->found && item->val.strval) {
511 free(item->val.strval);
513 item->val.strval = bstrdup(lc->str);
518 bool ini_store_name(LEX *lc, ConfigFile *inifile, ini_items *item)
521 Mmsg(inifile->edit, "%s", item->val.nameval);
524 if (lex_get_token(lc, T_NAME) == T_ERROR) {
525 Dmsg0(dbglevel, "Want token=T_NAME got T_ERROR\n");
528 Dmsg1(dbglevel, "ini_store_name: %s\n", lc->str);
529 strncpy(item->val.nameval, lc->str, sizeof(item->val.nameval));
534 bool ini_store_alist_str(LEX *lc, ConfigFile *inifile, ini_items *item)
538 /* TODO, write back the alist to edit buffer */
541 if (lex_get_token(lc, T_STRING) == T_ERROR) {
545 if (item->val.alistval == NULL) {
546 list = New(alist(10, owned_by_alist));
548 list = item->val.alistval;
551 Dmsg4(900, "Append %s to alist %p size=%d %s\n",
552 lc->str, list, list->size(), item->name);
553 list->append(bstrdup(lc->str));
554 item->val.alistval = list;
560 bool ini_store_pint64(LEX *lc, ConfigFile *inifile, ini_items *item)
563 Mmsg(inifile->edit, "%lld", item->val.int64val);
566 if (lex_get_token(lc, T_PINT64) == T_ERROR) {
569 item->val.int64val = lc->pint64_val;
574 bool ini_store_int64(LEX *lc, ConfigFile *inifile, ini_items *item)
577 Mmsg(inifile->edit, "%lld", item->val.int64val);
580 if (lex_get_token(lc, T_INT64) == T_ERROR) {
583 item->val.int64val = lc->int64_val;
588 bool ini_store_pint32(LEX *lc, ConfigFile *inifile, ini_items *item)
591 Mmsg(inifile->edit, "%d", item->val.int32val);
594 if (lex_get_token(lc, T_PINT32) == T_ERROR) {
597 item->val.int32val = lc->pint32_val;
602 bool ini_store_int32(LEX *lc, ConfigFile *inifile, ini_items *item)
605 Mmsg(inifile->edit, "%d", item->val.int32val);
608 if (lex_get_token(lc, T_INT32) == T_ERROR) {
611 item->val.int32val = lc->int32_val;
616 bool ini_store_bool(LEX *lc, ConfigFile *inifile, ini_items *item)
619 Mmsg(inifile->edit, "%s", item->val.boolval?"yes":"no");
622 if (lex_get_token(lc, T_NAME) == T_ERROR) {
625 if (strcasecmp(lc->str, "yes") == 0 ||
626 strcasecmp(lc->str, "true") == 0 ||
627 strcasecmp(lc->str, "on") == 0 ||
628 strcasecmp(lc->str, "1") == 0)
630 item->val.boolval = true;
632 } else if (strcasecmp(lc->str, "no") == 0 ||
633 strcasecmp(lc->str, "false") == 0 ||
634 strcasecmp(lc->str, "off") == 0 ||
635 strcasecmp(lc->str, "0") == 0)
637 item->val.boolval = false;
640 /* YES and NO must not be translated */
641 scan_err2(lc, _("Expect %s, got: %s"), "YES, NO, ON, OFF, 0, 1, TRUE, or FALSE", lc->str);
648 bool ini_store_date(LEX *lc, ConfigFile *inifile, ini_items *item)
651 bstrutime(inifile->edit,sizeof_pool_memory(inifile->edit),item->val.btimeval);
654 if (lex_get_token(lc, T_STRING) == T_ERROR) {
657 item->val.btimeval = str_to_utime(lc->str);
658 if (item->val.btimeval == 0) {
665 /* ---------------------------------------------------------------- */
668 * export LD_LIBRARY_PATH=.libs/
676 void _ok(const char *file, int l, const char *op, int value, const char *label)
681 printf("ERR %.45s %s:%i on %s\n", label, file, l, op);
683 printf("OK %.45s\n", label);
687 #define ok(x, label) _ok(__FILE__, __LINE__, #x, (x), label)
689 void _nok(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 nok(x, label) _nok(__FILE__, __LINE__, #x, (x), label)
704 printf("Result %i/%i OK\n", nb - err, nb);
708 struct ini_items membuf_items[] = {
709 /* name handler comment req */
710 {"client", ini_store_name, "Client name", 0},
711 {"serial", ini_store_int32, "Serial number", 1},
712 {"max_clients", ini_store_int32, "Max Clients", 0},
713 {NULL, NULL, NULL, 0}
717 struct ini_items test_items[] = {
718 /* name handler comment req */
719 {"datastore", ini_store_name, "Target Datastore", 0},
720 {"newhost", ini_store_str, "New Hostname", 1},
721 {"int64val", ini_store_int64, "Int64", 1},
722 {"list", ini_store_alist_str, "list", 0},
723 {"bool", ini_store_bool, "Bool", 0},
724 {"pint64", ini_store_pint64, "pint", 0},
725 {"int32", ini_store_int32, "int 32bit", 0},
726 {"plugin.test", ini_store_str, "test with .", 0},
727 {"adate", ini_store_date, "test with date", 0},
728 {NULL, NULL, NULL, 0}
731 /* In order to link with libbaccfg */
735 void save_resource(int type, RES_ITEM *items, int pass){}
736 void dump_resource(int type, RES *ares, void sendit(void *sock, const char *fmt, ...), void *sock){}
737 void free_resource(RES *rres, int type){}
740 RES_TABLE resources[] = {};
747 ConfigFile *ini = new ConfigFile();
748 POOLMEM *buf = get_pool_memory(PM_BSOCK);
752 printf("Begin Memory buffer Test\n");
753 ok(ini->register_items(membuf_items, sizeof(struct ini_items)), "Check sizeof ini_items");
755 if ((fp = fopen("test.cfg", "w")) == NULL) {
758 fprintf(fp, "client=JohnDoe\n");
759 fprintf(fp, "serial=2\n");
760 fprintf(fp, "max_clients=3\n");
762 fseek(fp, 0, SEEK_END);
766 if ((fp = fopen("test.cfg", "rb")) == NULL) {
767 printf("Could not open file test.cfg\n");
771 size = fread(buffer, 1, size, fp);
773 //printf("size of read buffer is: %d\n", size);
774 //printf("====buf=%s\n", buffer);
776 ok(ini->parse_buf(buffer), "Test memory read with all members");
782 printf("\n\nBegin Original Full Tests\n");
783 nok(ini->register_items(test_items, 5), "Check bad sizeof ini_items");
784 ok(ini->register_items(test_items, sizeof(struct ini_items)), "Check sizeof ini_items");
786 if ((fp = fopen("test.cfg", "w")) == NULL) {
789 fprintf(fp, "# this is a comment\ndatastore=datastore1\nnewhost=\"host1\"\n");
792 nok(ini->parse("test.cfg"), "Test missing member");
795 fprintf(fp, "int64val=12 # with a comment\n");
796 fprintf(fp, "int64val=10 # with a comment\n");
797 fprintf(fp, "int32=100\n");
798 fprintf(fp, "bool=yes\n");
799 fprintf(fp, "plugin.test=parameter\n");
800 fprintf(fp, "adate=\"1970-01-02 12:00:00\"\n");
804 ok(ini->parse("test.cfg"), "Test with all members");
806 ok(ini->items[0].found, "Test presence of char[]");
807 ok(!strcmp(ini->items[0].val.nameval, "datastore1"), "Test char[]");
808 ok(ini->items[1].found, "Test presence of char*");
809 ok(!strcmp(ini->items[1].val.strval, "host1"), "Test char*");
810 ok(ini->items[2].found, "Test presence of int");
811 ok(ini->items[2].val.int64val == 10, "Test int");
812 ok(ini->items[4].val.boolval == true, "Test bool");
813 ok(ini->items[6].val.int32val == 100, "Test int 32");
814 ok(ini->items[6].val.btimeval != 126000, "Test btime");
816 alist *list = ini->items[3].val.alistval;
817 nok(ini->items[3].found, "Test presence of alist");
819 fprintf(fp, "list=a\nlist=b\nlist=c\n");
823 ok(ini->parse("test.cfg"), "Test with all members");
825 list = ini->items[3].val.alistval;
826 ok(ini->items[3].found, "Test presence of alist");
827 ok(list != NULL, "Test list member");
828 ok(list->size() == 3, "Test list size");
830 ok(!strcmp((char *)list->get(0), "a"), "Testing alist[0]");
831 ok(!strcmp((char *)list->get(1), "b"), "Testing alist[1]");
832 ok(!strcmp((char *)list->get(2), "c"), "Testing alist[2]");
834 system("cp -f test.cfg test3.cfg");
836 fprintf(fp, "pouet='10, 11, 12'\n");
837 fprintf(fp, "pint=-100\n");
838 fprintf(fp, "int64val=-100\n"); /* TODO: fix negative numbers */
842 ok(ini->parse("test.cfg"), "Test with errors");
843 nok(ini->items[5].found, "Test presence of positive int");
850 if ((fp = fopen("test2.cfg", "w")) == NULL) {
854 "# this is a comment\n"
855 "optprompt=\"Datastore Name\"\n"
857 "optprompt=\"New Hostname to create\"\n"
859 "optprompt=\"Some 64 integer\"\n"
867 "plugin.test=@STR@\n"
872 ok(ini->unserialize("test2.cfg"), "Test dynamic parse");
873 ok(ini->serialize("test4.cfg"), "Try to dump the item table in a file");
874 ok(ini->serialize(&buf) > 0, "Try to dump the item table in a buffer");
875 ok(ini->parse("test3.cfg"), "Parse test file with dynamic grammar");
877 ok((pos = ini->get_item("datastore")) == 0, "Check datastore definition");
878 ok(ini->items[pos].found, "Test presence of char[]");
879 ok(!strcmp(ini->items[pos].val.nameval, "datastore1"), "Test char[]");
880 ok(!strcmp(ini->items[pos].comment, "Datastore Name"), "Check comment");
881 ok(ini->items[pos].required == false, "Check required");
883 ok((pos = ini->get_item("newhost")) == 1, "Check newhost definition");
884 ok(ini->items[pos].found, "Test presence of char*");
885 ok(!strcmp(ini->items[pos].val.strval, "host1"), "Test char*");
886 ok(ini->items[pos].required == false, "Check required");
888 ok((pos = ini->get_item("int64val")) == 2, "Check int64val definition");
889 ok(ini->items[pos].found, "Test presence of int");
890 ok(ini->items[pos].val.int64val == 10, "Test int");
891 ok(ini->items[pos].required == true, "Check required");
893 ok((pos = ini->get_item("bool")) == 4, "Check bool definition");
894 ok(ini->items[pos].val.boolval == true, "Test bool");
896 ok((pos = ini->get_item("adate")) == 9, "Check adate definition");
897 ok(ini->items[pos].val.btimeval == 126000, "Test date");
899 ok(ini->dump_results(&buf), "Test to dump results");
900 printf("<%s>\n", buf);
906 free_pool_memory(buf);