From 3231e88fec2af7ae0b495c25d422fb78f9d842ca Mon Sep 17 00:00:00 2001 From: Eric Bollengier Date: Wed, 4 Jan 2012 13:45:07 +0100 Subject: [PATCH] Add helper for ini files --- bacula/src/lib/Makefile.in | 18 +- bacula/src/lib/ini.c | 785 +++++++++++++++++++++++++++++++++++++ bacula/src/lib/ini.h | 220 +++++++++++ 3 files changed, 1021 insertions(+), 2 deletions(-) create mode 100644 bacula/src/lib/ini.c create mode 100644 bacula/src/lib/ini.h diff --git a/bacula/src/lib/Makefile.in b/bacula/src/lib/Makefile.in index 88f416abed..ab0d9c17f3 100644 --- a/bacula/src/lib/Makefile.in +++ b/bacula/src/lib/Makefile.in @@ -42,7 +42,7 @@ INCLUDE_FILES = ../baconfig.h ../bacula.h ../bc_types.h \ runscript.h rwlock.h serial.h sellist.h sha1.h \ smartall.h status.h tls.h tree.h var.h \ waitq.h watchdog.h workq.h \ - parse_conf.h \ + parse_conf.h ini.h \ pythonlib.h lockmgr.h devlock.h # @@ -58,7 +58,7 @@ LIBBAC_SRCS = attr.c base64.c berrno.c bsys.c binflate.c bget_msg.c \ rwlock.c scan.c sellist.c serial.c sha1.c \ signal.c smartall.c rblist.c tls.c tree.c \ util.c var.c watchdog.c workq.c btimers.c \ - address_conf.c breg.c htable.c lockmgr.c devlock.c + address_conf.c breg.c htable.c lockmgr.c devlock.c ini.c LIBBAC_OBJS = $(LIBBAC_SRCS:.c=.o) LIBBAC_LOBJS = $(LIBBAC_SRCS:.c=.lo) @@ -151,6 +151,13 @@ lockmgr_test: Makefile rm -f lockmgr.o $(CXX) $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) lockmgr.c +base64_test: Makefile + rm -f base64.o + $(CXX) -DBIN_TEST $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) base64.c + $(LIBTOOL_LINK) $(CXX) $(LDFLAGS) -L. -o $@ base64.o $(DLIB) -lbac -lm $(LIBS) $(OPENSSL_LIBS) + rm -f base64.o + $(CXX) $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) base64.c + rwlock_test: Makefile rm -f rwlock.o $(CXX) -DTEST_RWLOCK $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) rwlock.c @@ -200,6 +207,13 @@ bsnprintf: Makefile bsnprintf.o rm -f bsnprintf.o $(CXX) $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) bsnprintf.c +ini: Makefile ini.o + rm -f ini.o + $(CXX) -DTEST_PROGRAM $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) ini.c + $(LIBTOOL_LINK) $(CXX) $(LDFLAGS) -L. -o $@ ini.o -lbac $(DLIB) -lm $(LIBS) $(OPENSSL_LIBS) + rm -f ini.o + $(CXX) $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) ini.c + install-includes: $(MKDIR) $(DESTDIR)/$(includedir)/bacula for I in $(INCLUDE_FILES); do \ diff --git a/bacula/src/lib/ini.c b/bacula/src/lib/ini.c new file mode 100644 index 0000000000..61d9c39629 --- /dev/null +++ b/bacula/src/lib/ini.c @@ -0,0 +1,785 @@ +/* + Copyright (C) 2011-2011 Bacula Systems(R) SA + + The main author of Bacula is Kern Sibbald, with contributions from + many others, a complete list can be found in the file AUTHORS. + This program is Free Software; you can modify it under the terms of + version three of the GNU Affero General Public License as published by the + Free Software Foundation, which is listed in the file LICENSE. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. + + Bacula® is a registered trademark of Kern Sibbald. + Bacula Systems(R) is a trademark of Bacula Systems SA. + Bacula Enterprise(TM) is a trademark of Bacula Systems SA. + + The licensor of Bacula Enterprise(TM) is Bacula Systems(R) SA, + Rue Galilee 5, 1400 Yverdon-les-Bains, Switzerland. +*/ + +/* + * Handle simple configuration file such as "ini" files. + * key1 = val # comment + * key2 = val # + * + */ + +#include "bacula.h" +#include "ini.h" + +#define bfree_and_null_const(a) do{if(a){free((void *)a); (a)=NULL;}} while(0) +static int dbglevel = 100; + +/* We use this structure to associate a key to the function */ +struct ini_store { + const char *key; + const char *comment; + INI_ITEM_HANDLER *handler; +}; + +static struct ini_store funcs[] = { + {"@INT32@", "Integer", ini_store_int32}, + {"@PINT32@", "Integer", ini_store_pint32}, + {"@PINT64@", "Positive Integer", ini_store_pint64}, + {"@INT64@", "Integer", ini_store_int64}, + {"@NAME@", "Simple String", ini_store_name}, + {"@STR@", "String", ini_store_str}, + {"@BOOL@", "on/off", ini_store_bool}, + {"@ALIST@", "String list", ini_store_alist_str}, + {NULL, NULL, NULL} +}; + +/* + * Get handler code from handler @ + */ +const char *ini_get_store_code(INI_ITEM_HANDLER *handler) +{ + for (int i = 0; funcs[i].key ; i++) { + if (funcs[i].handler == handler) { + return funcs[i].key; + } + } + return NULL; +} + +/* + * Get handler function from handler name + */ +INI_ITEM_HANDLER *ini_get_store_handler(const char *key) +{ + for (int i = 0; funcs[i].key ; i++) { + if (!strcmp(funcs[i].key, key)) { + return funcs[i].handler; + } + } + return NULL; +} + +/* + * Format a scanner error message + */ +static void s_err(const char *file, int line, LEX *lc, const char *msg, ...) +{ + ConfigFile *ini = (ConfigFile *)(lc->caller_ctx); + va_list arg_ptr; + char buf[MAXSTRING]; + + va_start(arg_ptr, msg); + bvsnprintf(buf, sizeof(buf), msg, arg_ptr); + va_end(arg_ptr); + +#ifdef TEST_PROGRAM + printf("ERROR: Config file error: %s\n" + " : Line %d, col %d of file %s\n%s\n", + buf, lc->line_no, lc->col_no, lc->fname, lc->line); +#endif + + if (ini->jcr) { /* called from core */ + Jmsg(ini->jcr, M_ERROR, 0, _("Config file error: %s\n" + " : Line %d, col %d of file %s\n%s\n"), + buf, lc->line_no, lc->col_no, lc->fname, lc->line); + +// } else if (ini->ctx) { /* called from plugin */ +// ini->bfuncs->JobMessage(ini->ctx, __FILE__, __LINE__, M_FATAL, 0, +// _("Config file error: %s\n" +// " : Line %d, col %d of file %s\n%s\n"), +// buf, lc->line_no, lc->col_no, lc->fname, lc->line); +// + } else { /* called from ??? */ + e_msg(file, line, M_ERROR, 0, + _("Config file error: %s\n" + " : Line %d, col %d of file %s\n%s\n"), + buf, lc->line_no, lc->col_no, lc->fname, lc->line); + } +} + +/* Reset free items */ +void ConfigFile::clear_items() +{ + if (!items) { + return; + } + + for (int i=0; items[i].name; i++) { + if (items[i].found) { + /* special members require delete or free */ + if (items[i].handler == ini_store_str) { + free(items[i].val.strval); + items[i].val.strval = NULL; + + } else if (items[i].handler == ini_store_alist_str) { + delete items[i].val.alistval; + items[i].val.alistval = NULL; + } + items[i].found = false; + } + } +} + +void ConfigFile::free_items() +{ + if (items_allocated) { + for (int i=0; items[i].name; i++) { + bfree_and_null_const(items[i].name); + bfree_and_null_const(items[i].comment); + } + free(items); + } + items = NULL; + items_allocated = false; +} + +/* Get a particular item from the items list */ +int ConfigFile::get_item(const char *name) +{ + if (!items) { + return -1; + } + + for (int i=0; i < MAX_INI_ITEMS && items[i].name; i++) { + if (strcasecmp(name, items[i].name) == 0) { + return i; + } + } + return -1; +} + +/* Dump a buffer to a file in the working directory + * Needed to unserialise() a config + */ +bool ConfigFile::dump_string(const char *buf, int32_t len) +{ + FILE *fp; + bool ret=false; + + if (!out_fname) { + out_fname = get_pool_memory(PM_FNAME); + make_unique_filename(&out_fname, (int)(intptr_t)this, (char*)"configfile"); + } + + fp = fopen(out_fname, "wb"); + if (!fp) { + return ret; + } + + if (fwrite(buf, len, 1, fp) == 1) { + ret = true; + } + + fclose(fp); + return ret; +} + +/* Dump the item table format to a text file (used by plugin) */ +bool ConfigFile::serialize(const char *fname) +{ + FILE *fp; + POOLMEM *tmp; + int32_t len; + bool ret = false; + + if (!items) { + return ret; + } + + fp = fopen(fname, "w"); + if (!fp) { + return ret; + } + + tmp = get_pool_memory(PM_MESSAGE); + len = serialize(&tmp); + if (fwrite(tmp, len, 1, fp) == 1) { + ret = true; + } + free_pool_memory(tmp); + + fclose(fp); + return ret; +} + +/* Dump the item table format to a text file (used by plugin) */ +int ConfigFile::serialize(POOLMEM **buf) +{ + int len; + POOLMEM *tmp; + if (!items) { + **buf = 0; + return 0; + } + + len = Mmsg(buf, "# Plugin configuration file\n# Version %d\n", version); + + tmp = get_pool_memory(PM_MESSAGE); + + for (int i=0; items[i].name ; i++) { + if (items[i].comment) { + Mmsg(tmp, "OptPrompt=%s\n", items[i].comment); + pm_strcat(buf, tmp); + } + if (items[i].default_value) { + Mmsg(tmp, "OptDefault=%s\n", items[i].default_value); + pm_strcat(buf, tmp); + } + if (items[i].required) { + Mmsg(tmp, "OptRequired=yes\n"); + pm_strcat(buf, tmp); + } + + /* variable = @INT64@ */ + Mmsg(tmp, "%s=%s\n\n", + items[i].name, ini_get_store_code(items[i].handler)); + len = pm_strcat(buf, tmp); + } + free_pool_memory(tmp); + + return len ; +} + +/* Dump the item table content to a text file (used by director) */ +int ConfigFile::dump_results(POOLMEM **buf) +{ + int len; + POOLMEM *tmp; + if (!items) { + **buf = 0; + return 0; + } + len = Mmsg(buf, "# Plugin configuration file\n# Version %d\n", version); + + tmp = get_pool_memory(PM_MESSAGE); + + for (int i=0; items[i].name ; i++) { + if (items[i].found) { + items[i].handler(NULL, this, &items[i]); + if (items[i].comment && *items[i].comment) { + Mmsg(tmp, "# %s\n", items[i].comment); + pm_strcat(buf, tmp); + } + Mmsg(tmp, "%s=%s\n\n", items[i].name, this->edit); + len = pm_strcat(buf, tmp); + } + } + free_pool_memory(tmp); + + return len ; +} + +/* Parse a config file used by Plugin/Director */ +bool ConfigFile::parse(const char *fname) +{ + int token, i; + bool ret=false; + + if (!items) { + return false; + } + + if ((lc = lex_open_file(lc, fname, s_err)) == NULL) { + berrno be; + Emsg2(M_ERROR, 0, _("Cannot open config file %s: %s\n"), + fname, be.bstrerror()); + return false; + } + lc->options |= LOPT_NO_EXTERN; + lc->caller_ctx = (void *)this; + + while ((token=lex_get_token(lc, T_ALL)) != T_EOF) { + Dmsg1(dbglevel, "parse got token=%s\n", lex_tok_to_str(token)); + if (token == T_EOL) { + continue; + } + for (i=0; items[i].name; i++) { + if (strcasecmp(items[i].name, lc->str) == 0) { + if ((token = lex_get_token(lc, T_EQUALS)) == T_ERROR) { + Dmsg1(dbglevel, "in T_IDENT got token=%s\n", + lex_tok_to_str(token)); + break; + } + + Dmsg1(dbglevel, "calling handler for %s\n", items[i].name); + /* Call item handler */ + ret = items[i].found = items[i].handler(lc, this, &items[i]); + i = -1; + break; + } + } + if (i >= 0) { + Dmsg1(dbglevel, "Keyword = %s\n", lc->str); + scan_err1(lc, "Keyword %s not found", lc->str); + /* We can raise an error here */ + break; + } + if (!ret) { + break; + } + } + + for (i=0; items[i].name; i++) { + if (items[i].required && !items[i].found) { + scan_err1(lc, "%s required but not found", items[i].name); + ret = false; + } + } + + lc = lex_close_file(lc); + + return ret; +} + +/* Analyse the content of a ini file to build the item list + * It uses special syntax for datatype. Used by Director on Restore object + * + * OptPrompt = "Variable1" + * OptRequired + * OptDefault = 100 + * Variable1 = @PINT32@ + * ... + */ +bool ConfigFile::unserialize(const char *fname) +{ + int token, i, nb = 0; + bool ret=false; + const char **assign; + + /* At this time, we allow only 32 different items */ + int s = MAX_INI_ITEMS * sizeof (struct ini_items); + + items = (struct ini_items *) malloc (s); + memset(items, 0, s); + items_allocated = true; + + /* parse the file and generate the items structure on the fly */ + if ((lc = lex_open_file(lc, fname, s_err)) == NULL) { + berrno be; + Emsg2(M_ERROR, 0, _("Cannot open config file %s: %s\n"), + fname, be.bstrerror()); + return false; + } + lc->options |= LOPT_NO_EXTERN; + lc->caller_ctx = (void *)this; + + while ((token=lex_get_token(lc, T_ALL)) != T_EOF) { + Dmsg1(dbglevel, "parse got token=%s\n", lex_tok_to_str(token)); + + if (token == T_EOL) { + continue; + } + + ret = false; + assign = NULL; + + if (nb >= MAX_INI_ITEMS) { + break; + } + + if (strcasecmp("optprompt", lc->str) == 0) { + assign = &(items[nb].comment); + + } else if (strcasecmp("optdefault", lc->str) == 0) { + assign = &(items[nb].default_value); + + } else if (strcasecmp("optrequired", lc->str) == 0) { + items[nb].required = true; /* Don't use argument */ + scan_to_eol(lc); + continue; + + } else { + items[nb].name = bstrdup(lc->str); + } + + token = lex_get_token(lc, T_ALL); + Dmsg1(dbglevel, "in T_IDENT got token=%s\n", lex_tok_to_str(token)); + + if (token != T_EQUALS) { + scan_err1(lc, "expected an equals, got: %s", lc->str); + break; + } + + /* We may allow blank variable */ + if (lex_get_token(lc, T_STRING) == T_ERROR) { + break; + } + + if (assign) { + *assign = bstrdup(lc->str); + + } else { + if ((items[nb].handler = ini_get_store_handler(lc->str)) == NULL) { + scan_err1(lc, "expected a data type, got: %s", lc->str); + break; + } + nb++; + } + scan_to_eol(lc); + ret = true; + } + + if (!ret) { + for (i = 0; i < nb ; i++) { + bfree_and_null_const(items[i].name); + bfree_and_null_const(items[i].comment); + bfree_and_null_const(items[i].default_value); + items[i].handler = NULL; + items[i].required = false; + } + } + + lc = lex_close_file(lc); + return ret; +} + +/* ---------------------------------------------------------------- + * Handle data type. Import/Export + * ---------------------------------------------------------------- + */ +bool ini_store_str(LEX *lc, ConfigFile *inifile, ini_items *item) +{ + if (!lc) { + Mmsg(inifile->edit, "%s", item->val.strval); + return true; + } + if (lex_get_token(lc, T_STRING) == T_ERROR) { + return false; + } + /* If already allocated, free first */ + if (item->found && item->val.strval) { + free(item->val.strval); + } + item->val.strval = bstrdup(lc->str); + scan_to_eol(lc); + return true; +} + +bool ini_store_name(LEX *lc, ConfigFile *inifile, ini_items *item) +{ + if (!lc) { + Mmsg(inifile->edit, "%s", item->val.nameval); + return true; + } + if (lex_get_token(lc, T_NAME) == T_ERROR) { + return false; + } + strncpy(item->val.nameval, lc->str, sizeof(item->val.nameval)); + scan_to_eol(lc); + return true; +} + +bool ini_store_alist_str(LEX *lc, ConfigFile *inifile, ini_items *item) +{ + alist *list; + if (!lc) { + /* TODO, write back the alist to edit buffer */ + return true; + } + if (lex_get_token(lc, T_STRING) == T_ERROR) { + return false; + } + + if (item->val.alistval == NULL) { + list = New(alist(10, owned_by_alist)); + } else { + list = item->val.alistval; + } + + Dmsg4(900, "Append %s to alist %p size=%d %s\n", + lc->str, list, list->size(), item->name); + list->append(bstrdup(lc->str)); + item->val.alistval = list; + + scan_to_eol(lc); + return true; +} + +bool ini_store_pint64(LEX *lc, ConfigFile *inifile, ini_items *item) +{ + if (!lc) { + Mmsg(inifile->edit, "%lld", item->val.int64val); + return true; + } + if (lex_get_token(lc, T_PINT64) == T_ERROR) { + return false; + } + item->val.int64val = lc->pint64_val; + scan_to_eol(lc); + return true; +} + +bool ini_store_int64(LEX *lc, ConfigFile *inifile, ini_items *item) +{ + if (!lc) { + Mmsg(inifile->edit, "%lld", item->val.int64val); + return true; + } + if (lex_get_token(lc, T_INT64) == T_ERROR) { + return false; + } + item->val.int64val = lc->int64_val; + scan_to_eol(lc); + return true; +} + +bool ini_store_pint32(LEX *lc, ConfigFile *inifile, ini_items *item) +{ + if (!lc) { + Mmsg(inifile->edit, "%d", item->val.int32val); + return true; + } + if (lex_get_token(lc, T_PINT32) == T_ERROR) { + return false; + } + item->val.int32val = lc->pint32_val; + scan_to_eol(lc); + return true; +} + +bool ini_store_int32(LEX *lc, ConfigFile *inifile, ini_items *item) +{ + if (!lc) { + Mmsg(inifile->edit, "%d", item->val.int32val); + return true; + } + if (lex_get_token(lc, T_INT32) == T_ERROR) { + return false; + } + item->val.int32val = lc->int32_val; + scan_to_eol(lc); + return true; +} + +bool ini_store_bool(LEX *lc, ConfigFile *inifile, ini_items *item) +{ + if (!lc) { + Mmsg(inifile->edit, "%s", item->val.boolval?"yes":"no"); + return true; + } + if (lex_get_token(lc, T_NAME) == T_ERROR) { + return false; + } + if (strcasecmp(lc->str, "yes") == 0 || strcasecmp(lc->str, "true") == 0) { + item->val.boolval = true; + } else if (strcasecmp(lc->str, "no") == 0 || strcasecmp(lc->str, "false") == 0) { + item->val.boolval = false; + } else { + /* YES and NO must not be translated */ + scan_err2(lc, _("Expect %s, got: %s"), "YES, NO, TRUE, or FALSE", lc->str); + return false; + } + scan_to_eol(lc); + return true; +} + +/* ---------------------------------------------------------------- */ +#ifdef TEST_PROGRAM +/* make ini + * export LD_LIBRARY_PATH=.libs/ + * ./.libs/ini + */ + +#include + +int err=0; +int nb=0; +void _ok(const char *file, int l, const char *op, int value, const char *label) +{ + nb++; + if (!value) { + err++; + printf("ERR %.45s %s:%i on %s\n", label, file, l, op); + } else { + printf("OK %.45s\n", label); + } +} + +#define ok(x, label) _ok(__FILE__, __LINE__, #x, (x), label) + +void _nok(const char *file, int l, const char *op, int value, const char *label) +{ + nb++; + if (value) { + err++; + printf("ERR %.45s %s:%i on !%s\n", label, file, l, op); + } else { + printf("OK %.45s\n", label); + } +} + +#define nok(x, label) _nok(__FILE__, __LINE__, #x, (x), label) + +int report() +{ + printf("Result %i/%i OK\n", nb - err, nb); + return err>0; +} + +struct ini_items test_items[] = { + /* name handler comment req */ + {"datastore", ini_store_name, "Target Datastore", 0}, + {"newhost", ini_store_str, "New Hostname", 1}, + {"int64val", ini_store_int64, "Int64", 1}, + {"list", ini_store_alist_str, "list", 0}, + {"bool", ini_store_bool, "Bool", 0}, + {"pint64", ini_store_pint64, "pint", 0}, + {"int32", ini_store_int32, "int 32bit", 0}, + {"plugin.test", ini_store_str, "test with .", 0}, + {NULL, NULL, NULL, 0} +}; + +int main() +{ + FILE *fp; + int pos; + ConfigFile *ini = new ConfigFile(); + POOLMEM *buf = get_pool_memory(PM_BSOCK); + + nok(ini->register_items(test_items, 5), "Check bad sizeof ini_items"); + ok(ini->register_items(test_items, sizeof(struct ini_items)), "Check sizeof ini_items"); + + if ((fp = fopen("test.cfg", "w")) == NULL) { + exit (1); + } + fprintf(fp, "# this is a comment\ndatastore=datastore1\nnewhost=\"host1\"\n"); + fflush(fp); + + nok(ini->parse("test.cfg"), "Test missing member"); + ini->clear_items(); + + fprintf(fp, "int64val=12 # with a comment\n"); + fprintf(fp, "int64val=10 # with a comment\n"); + fprintf(fp, "int32=100\n"); + fprintf(fp, "bool=yes\n"); + fprintf(fp, "plugin.test=parameter\n"); + + fflush(fp); + + ok(ini->parse("test.cfg"), "Test with all members"); + + ok(ini->items[0].found, "Test presence of char[]"); + ok(!strcmp(ini->items[0].val.nameval, "datastore1"), "Test char[]"); + ok(ini->items[1].found, "Test presence of char*"); + ok(!strcmp(ini->items[1].val.strval, "host1"), "Test char*"); + ok(ini->items[2].found, "Test presence of int"); + ok(ini->items[2].val.int64val == 10, "Test int"); + ok(ini->items[4].val.boolval == true, "Test bool"); + ok(ini->items[6].val.int32val == 100, "Test int 32"); + + alist *list = ini->items[3].val.alistval; + nok(ini->items[3].found, "Test presence of alist"); + + fprintf(fp, "list=a\nlist=b\nlist=c\n"); + fflush(fp); + + ini->clear_items(); + ok(ini->parse("test.cfg"), "Test with all members"); + + list = ini->items[3].val.alistval; + ok(ini->items[3].found, "Test presence of alist"); + ok(list != NULL, "Test list member"); + ok(list->size() == 3, "Test list size"); + + ok(!strcmp((char *)list->get(0), "a"), "Testing alist[0]"); + ok(!strcmp((char *)list->get(1), "b"), "Testing alist[1]"); + ok(!strcmp((char *)list->get(2), "c"), "Testing alist[2]"); + + system("cp -f test.cfg test3.cfg"); + + fprintf(fp, "pouet='10, 11, 12'\n"); + fprintf(fp, "pint=-100\n"); + fprintf(fp, "int64val=-100\n"); /* TODO: fix negative numbers */ + fflush(fp); + + ini->clear_items(); + ok(ini->parse("test.cfg"), "Test with errors"); + nok(ini->items[5].found, "Test presence of positive int"); + + fclose(fp); + ini->clear_items(); + ini->free_items(); + + /* Test */ + if ((fp = fopen("test2.cfg", "w")) == NULL) { + exit (1); + } + fprintf(fp, + "# this is a comment\n" + "optprompt=\"Datastore Name\"\n" + "datastore=@NAME@\n" + "optprompt=\"New Hostname to create\"\n" + "newhost=@STR@\n" + "optprompt=\"Some 64 integer\"\n" + "optrequired=yes\n" + "int64val=@INT64@\n" + "list=@ALIST@\n" + "bool=@BOOL@\n" + "pint64=@PINT64@\n" + "pouet=@STR@\n" + "int32=@INT32@\n" + "plugin.test=@STR@\n" + ); + fclose(fp); + + ok(ini->unserialize("test2.cfg"), "Test dynamic parse"); + ok(ini->serialize("test4.cfg"), "Try to dump the item table in a file"); + ok(ini->serialize(&buf) > 0, "Try to dump the item table in a buffer"); + ok(ini->parse("test3.cfg"), "Parse test file with dynamic grammar"); + + ok((pos = ini->get_item("datastore")) == 0, "Check datastore definition"); + ok(ini->items[pos].found, "Test presence of char[]"); + ok(!strcmp(ini->items[pos].val.nameval, "datastore1"), "Test char[]"); + ok(!strcmp(ini->items[pos].comment, "Datastore Name"), "Check comment"); + ok(ini->items[pos].required == false, "Check required"); + + ok((pos = ini->get_item("newhost")) == 1, "Check newhost definition"); + ok(ini->items[pos].found, "Test presence of char*"); + ok(!strcmp(ini->items[pos].val.strval, "host1"), "Test char*"); + ok(ini->items[pos].required == false, "Check required"); + + ok((pos = ini->get_item("int64val")) == 2, "Check int64val definition"); + ok(ini->items[pos].found, "Test presence of int"); + ok(ini->items[pos].val.int64val == 10, "Test int"); + ok(ini->items[pos].required == true, "Check required"); + + ok((pos = ini->get_item("bool")) == 4, "Check bool definition"); + ok(ini->items[pos].val.boolval == true, "Test bool"); + + ok(ini->dump_results(&buf), "Test to dump results"); + printf("<%s>\n", buf); + + ini->clear_items(); + ini->free_items(); + report(); + + free_pool_memory(buf); + exit (0); +} + + +#endif diff --git a/bacula/src/lib/ini.h b/bacula/src/lib/ini.h new file mode 100644 index 0000000000..aa82bcdb5a --- /dev/null +++ b/bacula/src/lib/ini.h @@ -0,0 +1,220 @@ +/* + Copyright (C) 2011-2011 Bacula Systems(R) SA + + The main author of Bacula is Kern Sibbald, with contributions from + many others, a complete list can be found in the file AUTHORS. + This program is Free Software; you can modify it under the terms of + version three of the GNU Affero General Public License as published by the + Free Software Foundation, which is listed in the file LICENSE. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. + + Bacula® is a registered trademark of Kern Sibbald. + Bacula Systems(R) is a trademark of Bacula Systems SA. + Bacula Enterprise(TM) is a trademark of Bacula Systems SA. + + The licensor of Bacula Enterprise(TM) is Bacula Systems(R) SA, + Rue Galilee 5, 1400 Yverdon-les-Bains, Switzerland. +*/ + +#ifndef INI_H +#define INI_H + +/* + * Plugin has a internal C structure that describes the configuration: + * struct ini_items[] + * + * The ConfigFile object can generate a text file that describes the C + * structure. This text format is saved as RestoreObject in the catalog. + * + * struct ini_items[] -> register_items() -> serialize() -> RestoreObject R1 + * + * On the Director side, at the restore time, we can analyse this text to + * get the C structure. + * + * RestoreObject R1 -> write to disk -> unserialize() -> struct ini_items[] + * + * Once done, we can ask questions to the user at the restore time and fill + * the C struct with answers. The Director can send back as a RestoreObject + * the result of the questionnaire. + * + * struct ini_items[] -> UAContext -> dump_result() -> FD as RestoreObject R2 + * + * On the Plugin side, it can get back the C structure and use it. + * RestoreObject R2 -> parse() -> struct ini_items[] + */ + +class ConfigFile; +struct ini_items; + +/* Used to store result */ +typedef union { + char *strval; + char nameval[MAX_NAME_LENGTH]; + int64_t int64val; + int32_t int32val; + alist *alistval; + bool boolval; +} item_value; + +/* These functions are used to convert a string to the appropriate value */ +typedef +bool (INI_ITEM_HANDLER)(LEX *lc, ConfigFile *inifile, + struct ini_items *item); + +/* If no items are registred at the scan time, we detect this list from + * the file itself + */ +struct ini_items { + const char *name; /* keyword name */ + INI_ITEM_HANDLER *handler; /* type accepted */ + const char *comment; /* comment associated, used in prompt */ + + int required; /* optional required or not */ + const char *re_value; /* optional regexp associated */ + const char *in_values; /* optional list of values */ + const char *default_value; /* optional default value */ + + bool found; /* if val is set */ + item_value val; /* val contains the value */ +}; + +/* When reading a ini file, we limit the number of items that we + * can create + */ +#define MAX_INI_ITEMS 32 + +/* Special RestoreObject name used to get user input at restore time */ +#define INI_RESTORE_OBJECT_NAME "RestoreOptions" + +/* Can be used to set re_value, in_value, default_value, found and val to 0 + * G++ looks to allow partial declaration, let see with an other compiler + */ +#define ITEMS_DEFAULT NULL,NULL,NULL,0,{0} + +/* + * Handle simple configuration file such as "ini" files. + * key1 = val # comment + * OptPrompt=comment + * key2 = val + * + * For usage example, see ini.c TEST_PROGRAM + */ + +class ConfigFile +{ +private: + LEX *lc; /* Lex parser */ + bool items_allocated; + +public: + JCR *jcr; /* JCR needed for Jmsg */ + int version; /* Internal version check */ + int sizeof_ini_items; /* Extra check when using dynamic loading */ + struct ini_items *items; /* Structure of the config file */ + POOLMEM *out_fname; /* Can be used to dump config to disk */ + POOLMEM *edit; /* Can be used to build result file */ + + ConfigFile() { + lc = NULL; + jcr = NULL; + items = NULL; + out_fname = NULL; + + version = 1; + items_allocated = false; + edit = get_pool_memory(PM_FNAME); + sizeof_ini_items = sizeof(struct ini_items); + } + + ~ConfigFile() { + if (lc) { + lex_close_file(lc); + } + if (edit) { + free_pool_memory(edit); + } + if (out_fname) { + unlink(out_fname); + free_pool_memory(out_fname); + } + free_items(); + } + + /* Dump a config string to out_fname */ + bool dump_string(const char *buf, int32_t len); + + /* JCR needed for Jmsg */ + void set_jcr(JCR *ajcr) { + jcr = ajcr; + } + + /* Free malloced items such as char* or alist or items */ + void free_items(); + + /* Clear items member */ + void clear_items(); + + /* Dump the item table to a file (used on plugin side) */ + bool serialize(const char *fname); + + /* Dump the item table format to a buffer (used on plugin side) + * returns the length of the buffer, -1 if error + */ + int serialize(POOLMEM **buf); + + /* Dump the item table content to a buffer */ + int dump_results(POOLMEM **buf); + + /* Get item position in items list (useful when dynamic) */ + int get_item(const char *name); + + /* Register config file structure, if size doesn't match */ + bool register_items(struct ini_items *aitems, int size) { + int i; + if (sizeof_ini_items == size) { + for (i = 0; aitems[i].name ; i++); + items = (struct ini_items*) malloc((i+1) * size); /* NULL terminated */ + memcpy(items, aitems, (i+1) * size); + items_allocated = false; /* we copy only pointers, don't free them */ + return true; + } + return false; + } + + /* Parse a ini file with a item list previously registred (plugin side) */ + bool parse(const char *filename); + + /* Create a item list from a ini file (director side) */ + bool unserialize(const char *filename); +}; + +/* + * Standard global parsers defined in ini.c + * When called with lc=NULL, it converts the item value back in inifile->edit + * buffer. + */ +bool ini_store_str(LEX *lc, ConfigFile *inifile, ini_items *item); +bool ini_store_name(LEX *lc, ConfigFile *inifile, ini_items *item); +bool ini_store_alist_str(LEX *lc, ConfigFile *inifile, ini_items *item); +bool ini_store_pint64(LEX *lc, ConfigFile *inifile, ini_items *item); +bool ini_store_int64(LEX *lc, ConfigFile *inifile, ini_items *item); +bool ini_store_pint32(LEX *lc, ConfigFile *inifile, ini_items *item); +bool ini_store_int32(LEX *lc, ConfigFile *inifile, ini_items *item); +bool ini_store_bool(LEX *lc, ConfigFile *inifile, ini_items *item); + +/* Get handler code from handler @ */ +const char *ini_get_store_code(INI_ITEM_HANDLER *handler); + +/* Get handler function from handler name */ +INI_ITEM_HANDLER *ini_get_store_handler(const char *); + +#endif -- 2.39.2