From: Radosław Korzeniewski Date: Wed, 23 Dec 2020 16:45:31 +0000 (+0100) Subject: pluginlib: Refactoring PLUGINPREFIX to C++. X-Git-Tag: Release-11.3.2~730 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c3e9be850fd5b3637a60284701fb7feda28ded73;p=thirdparty%2Fbacula.git pluginlib: Refactoring PLUGINPREFIX to C++. --- diff --git a/bacula/src/plugins/fd/pluginlib/Makefile.in b/bacula/src/plugins/fd/pluginlib/Makefile.in index 914bb58db..de9a16f5e 100644 --- a/bacula/src/plugins/fd/pluginlib/Makefile.in +++ b/bacula/src/plugins/fd/pluginlib/Makefile.in @@ -14,6 +14,7 @@ SRCDIR = ../../.. FDDIR = $(SRCDIR)/filed LIBDIR = $(SRCDIR)/lib FINDLIBDIR = $(SRCDIR)/findlib +FDPLUGDIR=.. topdir = @BUILD_DIR@ working_dir=@working_dir@ @@ -32,6 +33,8 @@ PTCOMMOBJ = $(filter %.lo,$(PTCOMMSRC:.cpp=.lo)) COMMCTXSRC = commctx.h SMARTALISTSRC = smartalist.h SMARTPTRSRC = smartalist.h +METAPLUGINSRC = metaplugin.cpp metaplugin.h +METAPLUGINOBJ = $(filter %.lo,$(METAPLUGIN:.cpp=.lo)) PLUGINLIBSTEST = pluginlib_test.cpp $(PLUGINLIBSSRC) $(UNITTESTSOBJ) PLUGINLIBSTESTOBJ = $(filter %.lo,$(PLUGINLIBSTEST:.cpp=.lo)) @@ -58,15 +61,15 @@ LIBBAC = -lbac -L$(LIBDIR)/.libs .c.lo: @echo "Compiling $< ..." - $(NO_ECHO)$(LIBTOOL_COMPILE) $(CXX) $(DEFS) $(DEBUG) $(CPPFLAGS) $(CFLAGS) -I$(SRCDIR) -I$(FDDIR) -I$(LIBDIR) -I$(FINDLIBDIR) -I. -c $< + $(NO_ECHO)$(LIBTOOL_COMPILE) $(CXX) $(DEFS) $(DEBUG) $(CPPFLAGS) $(CFLAGS) -I$(SRCDIR) -I$(FDDIR) -I$(FDPLUGDIR) -I$(LIBDIR) -I$(FINDLIBDIR) -I. -c $< .cpp.lo: @echo "Compiling c++ $< ..." - $(NO_ECHO)$(LIBTOOL_COMPILE) $(CXX) $(DEFS) $(DEBUG) $(CPPFLAGS) $(CFLAGS) -I$(SRCDIR) -I$(FDDIR) -I$(LIBDIR) -I$(FINDLIBDIR) -I. -c $< + $(NO_ECHO)$(LIBTOOL_COMPILE) $(CXX) $(DEFS) $(DEBUG) $(CPPFLAGS) $(CFLAGS) -I$(SRCDIR) -I$(FDDIR) -I$(FDPLUGDIR) -I$(LIBDIR) -I$(FINDLIBDIR) -I. -c $< %.lo: %.cpp %.h @echo "Pattern compiling c++ $< ..." - $(NO_ECHO)$(LIBTOOL_COMPILE) $(CXX) $(DEFS) $(DEBUG) $(CPPFLAGS) $(CFLAGS) -I$(SRCDIR) -I$(FDDIR) -I$(LIBDIR) -I$(FINDLIBDIR) -I. -c $< + $(NO_ECHO)$(LIBTOOL_COMPILE) $(CXX) $(DEFS) $(DEBUG) $(CPPFLAGS) $(CFLAGS) -I$(SRCDIR) -I$(FDDIR) -I$(FDPLUGDIR) -I$(LIBDIR) -I$(FINDLIBDIR) -I. -c $< all: $(COMMONPLUGINOBJ) $(COMMONPLUGINTESTS) $(TESTMETAPLUGINBACKENDOBJ) diff --git a/bacula/src/plugins/fd/pluginlib/metaplugin.cpp b/bacula/src/plugins/fd/pluginlib/metaplugin.cpp new file mode 100644 index 000000000..c0a51181e --- /dev/null +++ b/bacula/src/plugins/fd/pluginlib/metaplugin.cpp @@ -0,0 +1,2667 @@ +/* + Bacula® - The Network Backup Solution + + Copyright (C) 2007-2017 Bacula Systems SA + All rights reserved. + + The main author of Bacula is Kern Sibbald, with contributions from many + others, a complete list can be found in the file AUTHORS. + + Licensees holding a valid Bacula Systems SA license may use this file + and others of this release in accordance with the proprietary license + agreement provided in the LICENSE file. Redistribution of any part of + this release is not permitted. + + Bacula® is a registered trademark of Kern Sibbald. +*/ +/** + * @file metaplugin.h + * @author Radosław Korzeniewski (radoslaw@korzeniewski.net) + * @brief This is a Bacula metaplugin interface. + * @version 2.1.0 + * @date 2020-12-23 + * + * @copyright Copyright (c) 2020 All rights reserved. IP transferred to Bacula Systems according to agreement. + */ + +#include "metaplugin.h" +#include +#include +#include + +/* + * libbac uses its own sscanf implementation which is not compatible with + * libc implementation, unfortunately. + * use bsscanf for Bacula sscanf flavor + */ +#ifdef sscanf +#undef sscanf +#endif +#define NEED_REVIEW + +#define pluginclass(ctx) (METAPLUGIN*)ctx->pContext; + +// Job Info Types +#define BACKEND_JOB_INFO_BACKUP 'B' +#define BACKEND_JOB_INFO_ESTIMATE 'E' +#define BACKEND_JOB_INFO_RESTORE 'R' + +/* Forward referenced functions */ +static bRC newPlugin(bpContext *ctx); +static bRC freePlugin(bpContext *ctx); +static bRC getPluginValue(bpContext *ctx, pVariable var, void *value); +static bRC setPluginValue(bpContext *ctx, pVariable var, void *value); +static bRC handlePluginEvent(bpContext *ctx, bEvent *event, void *value); +static bRC startBackupFile(bpContext *ctx, struct save_pkt *sp); +static bRC endBackupFile(bpContext *ctx); +static bRC pluginIO(bpContext *ctx, struct io_pkt *io); +static bRC startRestoreFile(bpContext *ctx, const char *cmd); +static bRC endRestoreFile(bpContext *ctx); +static bRC createFile(bpContext *ctx, struct restore_pkt *rp); +static bRC setFileAttributes(bpContext *ctx, struct restore_pkt *rp); +// Not used! static bRC checkFile(bpContext *ctx, char *fname); +static bRC handleXACLdata(bpContext *ctx, struct xacl_pkt *xacl); +static bRC queryParameter(bpContext *ctx, struct query_pkt *qp); + +/* Pointers to Bacula functions */ +bFuncs *bfuncs = NULL; +bInfo *binfo = NULL; + +static pFuncs pluginFuncs = +{ + sizeof(pluginFuncs), + FD_PLUGIN_INTERFACE_VERSION, + + /* Entry points into plugin */ + newPlugin, + freePlugin, + getPluginValue, + setPluginValue, + handlePluginEvent, + startBackupFile, + endBackupFile, + startRestoreFile, + endRestoreFile, + pluginIO, + createFile, + setFileAttributes, + NULL, + handleXACLdata, + NULL, /* No restore file list */ + NULL, /* No checkStream */ + queryParameter, +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/* Plugin Information structure */ +static pInfo pluginInfo = { + sizeof(pluginInfo), + FD_PLUGIN_INTERFACE_VERSION, + FD_PLUGIN_MAGIC, + PLUGIN_LICENSE, + PLUGIN_AUTHOR, + PLUGIN_DATE, + PLUGIN_VERSION, + PLUGIN_DESCRIPTION, +}; + +/* + * Plugin called here when it is first loaded + */ +bRC DLL_IMP_EXP loadPlugin(bInfo *lbinfo, bFuncs *lbfuncs, pInfo ** pinfo, pFuncs ** pfuncs) +{ + bfuncs = lbfuncs; /* set Bacula function pointers */ + binfo = lbinfo; + char *exepath; + POOL_MEM backend(PM_FNAME); + + Dmsg3(DINFO, "%s Plugin version %s %s (c) 2020 by Inteos\n", PLUGINNAME, PLUGIN_VERSION, PLUGIN_DATE); + + *pinfo = &pluginInfo; /* return pointer to our info */ + *pfuncs = &pluginFuncs; /* return pointer to our functions */ + + return bRC_OK; +} + +/* + * Plugin called here when it is unloaded, normally when Bacula is going to exit. + */ +bRC DLL_IMP_EXP unloadPlugin() +{ + return bRC_OK; +} + +#ifdef __cplusplus +} +#endif + +/* + * Backend Context constructor, initializes variables. + * The BackendCTX class is a simple storage class used for storage variables + * for context data. It is used for proper variable allocation and freeing only. + * + * in: + * command - the Plugin command string + * out: + * allocated variables + */ +BackendCTX::BackendCTX(char *command) +{ + cmd = bstrdup(command); + comm = New(PTCOMM()); + parser = NULL; + ini = NULL; +} + +/* + * Backend Context destructor, handles variable freeing on delete. + * + * in: + * none + * out: + * freed internal variables and class allocated during job execution + */ +BackendCTX::~BackendCTX() +{ + if (cmd){ + // printf("~BackendCTX:cmd:%p\n", cmd); + free(cmd); + } + if (comm){ + // printf("~BackendCTX:comm:%p\n", comm); + delete comm; + } + if (parser){ + // printf("~BackendCTX:parser:%p\n", parser); + delete parser; + } + if (ini){ + // printf("~BackendCTX:ini:%p\n", ini); + delete ini; + } +} + +/* + * Main PLUGIN class constructor. + */ +METAPLUGIN::METAPLUGIN(bpContext *bpctx) : + backend_cmd(PM_FNAME), + ctx(NULL), + mode(MODE::NONE), + JobId(0), + JobName(NULL), + since(0), + where(NULL), + regexwhere(NULL), + replace(0), + robjsent(false), + estimate(false), + listing(ListingNone), + nodata(false), + nextfile(false), + openerror(false), + pluginobject(false), + readacl(false), + readxattr(false), + accurate_warning(false), + backendctx(NULL), + backendlist(NULL), + fname(PM_FNAME), + lname(NULL), + robjbuf(NULL), + plugin_obj_cat(PM_FNAME), + plugin_obj_type(PM_FNAME), + plugin_obj_name(PM_FNAME), + plugin_obj_src(PM_FNAME), + plugin_obj_uuid(PM_FNAME), + plugin_obj_size(PM_FNAME), + acldatalen(0), + acldata(PM_MESSAGE), + xattrdatalen(0), + xattrdata(PM_MESSAGE) +{ + /* TODO: we have a ctx variable stored internally, decide if we use it + * for every method or rip it off as not required in our code */ + ctx = bpctx; +} + +/* + * Main PLUGIN class destructor, handles variable freeing on delete. + * + * in: + * none + * out: + * freed internal variables and class allocated during job execution + */ +METAPLUGIN::~METAPLUGIN() +{ + BackendCTX *bctx; + + /* free standard variables */ + free_and_null_pool_memory(lname); + free_and_null_pool_memory(robjbuf); + /* free backend contexts */ + if (backendlist){ + // printf("~PLUGIN:backendlist:%p\n", backendlist); + /* free all backend contexts */ + foreach_alist(bctx, backendlist){ + // printf("~PLUGIN:bctx:%p\n", bctx); + delete bctx; + } + delete backendlist; + } +} + +/* + * Check if a parameter (param) exist in ConfigFile variables set by user. + * The checking ignore case of the parameter. + * + * in: + * ini - a pointer to the ConfigFile class which has parsed user parameters + * param - a parameter to search in ini parameter keys + * out: + * -1 - when a parameter param is not found in ini keys + * - whan a parameter param is found and is an index in ini->items table + */ +int METAPLUGIN::check_ini_param(ConfigFile *ini, char *param) +{ + int k; + + for (k = 0; ini->items[k].name; k++){ + if (ini->items[k].found && strcasecmp(param, ini->items[k].name) == 0){ + return k; + } + } + return -1; +} + +/* + * Search if parameter (param) is on parameter list prepared for backend. + * The checking ignore case of the parameter. + * + * in: + * param - the parameter which we are looking for + * params - the list of parameters to search + * out: + * True when the parameter param is found in list + * False when we can't find the param on list + */ +bool METAPLUGIN::check_plugin_param(const char *param, alist *params) +{ + POOLMEM *par; + char *equal; + bool found = false; + + foreach_alist(par, params){ + equal = strchr(par, '='); + if (equal){ + /* temporary terminate the par at parameter name */ + *equal = '\0'; + if (strcasecmp(par, param) == 0){ + found = true; + } + /* restore parameter equal sign */ + *equal = '='; + } else { + if (strcasecmp(par, param) == 0){ + found = true; + } + } + } + return found; +} + +/* + * Counts the number of ini->items available as it is a NULL terminated array. + * + * in: + * ini - a pointer to ConfigFile class + * out: + * - the number of ini->items + */ +int METAPLUGIN::get_ini_count(ConfigFile *ini) +{ + int k; + int count = 0; + + if (ini){ + for (k=0; ini->items[k].name; k++){ + if (ini->items[k].found) { + count++; + } + } + } + return count; +} + +/* + * + */ +bRC METAPLUGIN::render_param(bpContext* ctx, POOLMEM *param, INI_ITEM_HANDLER *handler, char *key, item_value val) +{ + if (handler == ini_store_str){ + Mmsg(¶m, "%s=%s\n", key, val.strval); + } else + if (handler == ini_store_int64){ + Mmsg(¶m, "%s=%lld\n", key, val.int64val); + } else + if (handler == ini_store_bool){ + Mmsg(¶m, "%s=%d\n", key, val.boolval ? 1 : 0); + } else { + DMSG1(ctx, DERROR, "Unsupported parameter handler for: %s\n", key); + JMSG1(ctx, M_FATAL, "Unsupported parameter handler for: %s\n", key); + return bRC_Error; + } + return bRC_OK; +} + +/* + * Parsing a plugin command. + * + * in: + * bpContext - Bacula Plugin context structure + * command - plugin command string to parse + * out: + * bRC_OK - on success + * bRC_Error - on error + */ +bRC METAPLUGIN::parse_plugin_command(bpContext *ctx, const char *command, alist *params) +{ + int i, k; + bool found; + int count; + int parargc, argc; + POOLMEM *param; + + // debug only + // dump_backendlist(ctx); + + DMSG(ctx, DINFO, "Parse command: %s\n", command); + if (backendctx->parser != NULL){ + DMSG0(ctx, DINFO, "already parsed command.\n"); + return bRC_OK; + } + + /* allocate a new parser and parse command */ + backendctx->parser = new cmd_parser(); + if (backendctx->parser->parse_cmd(command) != bRC_OK) { + DMSG0(ctx, DERROR, "Unable to parse Plugin command line.\n"); + JMSG0(ctx, M_FATAL, "Unable to parse Plugin command line.\n"); + return bRC_Error; + } + + /* count the numbers of user parameters if any */ + count = get_ini_count(backendctx->ini); + + /* the first (zero) parameter is a plugin name, we should skip it */ + argc = backendctx->parser->argc - 1; + parargc = argc + count; + /* first parameters from plugin command saved during backup */ + for (i = 1; i < backendctx->parser->argc; i++) { + param = get_pool_memory(PM_FNAME); + found = false; + /* check if parameter overloaded by restore parameter */ + if (backendctx->ini){ + if ((k = check_ini_param(backendctx->ini, backendctx->parser->argk[i])) != -1){ + found = true; + DMSG1(ctx, DINFO, "parse_plugin_command: %s found in restore parameters\n", backendctx->parser->argk[i]); + if (render_param(ctx, param, backendctx->ini->items[k].handler, backendctx->parser->argk[i], backendctx->ini->items[k].val) != bRC_OK){ + free_and_null_pool_memory(param); + return bRC_Error; + } + params->append(param); + parargc--; + } + } + /* check if param overloaded above */ + if (!found){ + if (backendctx->parser->argv[i]){ + Mmsg(¶m, "%s=%s\n", backendctx->parser->argk[i], backendctx->parser->argv[i]); + params->append(param); + } else { + Mmsg(¶m, "%s=1\n", backendctx->parser->argk[i]); + params->append(param); + } + } + /* param is always ended with '\n' */ + DMSG(ctx, DINFO, "Param: %s", param); + + /* scan for abort_on_error parameter */ + if (strcasecmp(backendctx->parser->argk[i], "abort_on_error") == 0){ + /* found, so check the value if provided, I only check the first char */ + if (backendctx->parser->argv[i] && *backendctx->parser->argv[i] == '0'){ + backendctx->comm->clear_abort_on_error(); + } else { + backendctx->comm->set_abort_on_error(); + } + DMSG1(ctx, DINFO, "abort_on_error found: %s\n", backendctx->comm->is_abort_on_error() ? "True" : "False"); + } + /* scan for listing parameter, so the estimate job should be executed as a Listing procedure */ + if (strcasecmp(backendctx->parser->argk[i], "listing") == 0){ + /* found, so check the value if provided */ + if (backendctx->parser->argv[i]){ + listing = Listing; + DMSG0(ctx, DINFO, "listing procedure param found\n"); + } + } + /* scan for query parameter, so the estimate job should be executed as a QueryParam procedure */ + if (strcasecmp(backendctx->parser->argk[i], "query") == 0){ + /* found, so check the value if provided */ + if (backendctx->parser->argv[i]){ + listing = QueryParams; + DMSG0(ctx, DINFO, "query procedure param found\n"); + } + } + } + /* check what was missing in plugin command but get from ini file */ + if (argc < parargc){ + for (k = 0; backendctx->ini->items[k].name; k++){ + if (backendctx->ini->items[k].found && !check_plugin_param(backendctx->ini->items[k].name, params)){ + param = get_pool_memory(PM_FNAME); + DMSG1(ctx, DINFO, "parse_plugin_command: %s from restore parameters\n", backendctx->ini->items[k].name); + if (render_param(ctx, param, backendctx->ini->items[k].handler, (char*)backendctx->ini->items[k].name, backendctx->ini->items[k].val) != bRC_OK){ + free_and_null_pool_memory(param); + return bRC_Error; + } + params->append(param); + /* param is always ended with '\n' */ + DMSG(ctx, DINFO, "Param: %s", param); + } + } + } + + return bRC_OK; +} + +/* + * Parse a Restore Object saved during backup and modified by user during restore. + * Every RO received will generate a dedicated backend context which is used + * by bEventRestoreCommand to handle backend parameters for restore. + * + * in: + * bpContext - Bacula Plugin context structure + * rop - a restore object structure to parse + * out: + * bRC_OK - on success + * bRC_Error - on error + */ +bRC METAPLUGIN::parse_plugin_restoreobj(bpContext *ctx, restore_object_pkt *rop) +{ + int i; + BackendCTX *pini; + + if (!rop){ + return bRC_OK; /* end of rop list */ + } + // debug only + // dump_backendlist(ctx); + + if (!strcmp(rop->object_name, INI_RESTORE_OBJECT_NAME)) { + /* we have a single RO for every backend */ + if (backendlist == NULL){ + /* new backend list required */ + new_backendlist(ctx, rop->plugin_name); + pini = backendctx; + } else { + /* backend list available, so allocate the new context */ + pini = New(BackendCTX(rop->plugin_name)); + backendlist->append(pini); + } + + DMSG(ctx, DINFO, "INIcmd: %s\n", pini->cmd); + pini->ini = new ConfigFile(); + if (!pini->ini->dump_string(rop->object, rop->object_len)) { + DMSG0(ctx, DERROR, "ini->dump_string failed\n"); + goto bailout; + } + pini->ini->register_items(plugin_items_dump, sizeof(struct ini_items)); + if (!pini->ini->parse(pini->ini->out_fname)){ + DMSG0(ctx, DERROR, "ini->parse failed\n"); + goto bailout; + } + for (i=0; pini->ini->items[i].name; i++){ + if (pini->ini->items[i].found){ + if (pini->ini->items[i].handler == ini_store_str){ + DMSG2(ctx, DINFO, "INI: %s = %s\n", pini->ini->items[i].name, pini->ini->items[i].val.strval); + } else + if (pini->ini->items[i].handler == ini_store_int64){ + DMSG2(ctx, DINFO, "INI: %s = %lld\n", pini->ini->items[i].name, pini->ini->items[i].val.int64val); + } else + if (pini->ini->items[i].handler == ini_store_bool){ + DMSG2(ctx, DINFO, "INI: %s = %s\n", pini->ini->items[i].name, pini->ini->items[i].val.boolval ? "True" : "False"); + } else { + DMSG1(ctx, DERROR, "INI: unsupported parameter handler for: %s\n", pini->ini->items[i].name); + JMSG1(ctx, M_FATAL, "INI: unsupported parameter handler for: %s\n", pini->ini->items[i].name); + goto bailout; + } + } + } + } + // for debug only + // dump_backendlist(ctx); + return bRC_OK; + +bailout: + JMSG0(ctx, M_FATAL, "Unable to parse user set restore configuration.\n"); + if (pini){ + if (pini->ini){ + delete pini->ini; + pini->ini = NULL; + } + } + return bRC_Error; +} + +/* + * Run external backend script/application using BACKEND_CMD compile variable. + * It will run the backend in current backend context (backendctx) and should + * be called when a new backend is really required only. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * out: + * bRC_OK - when backend spawned successfully + * bRC_Error - when Plugin cannot run backend + */ +bRC METAPLUGIN::run_backend(bpContext *ctx) +{ + BPIPE *bp; + + if (access(backend_cmd.c_str(), X_OK) < 0){ + berrno be; + DMSG2(ctx, DERROR, "Unable to access backend: %s Err=%s\n", backend_cmd.c_str(), be.bstrerror()); + JMSG2(ctx, M_FATAL, "Unable to access backend: %s Err=%s\n", backend_cmd.c_str(), be.bstrerror()); + return bRC_Error; + } + DMSG(ctx, DINFO, "Executing: %s\n", backend_cmd.c_str()); + bp = open_bpipe(backend_cmd.c_str(), 0, "rwe"); + if (bp == NULL){ + berrno be; + DMSG(ctx, DERROR, "Unable to run backend. Err=%s\n", be.bstrerror()); + JMSG(ctx, M_FATAL, "Unable to run backend. Err=%s\n", be.bstrerror()); + return bRC_Error; + } + /* setup communication channel */ + backendctx->comm->set_bpipe(bp); + DMSG(ctx, DINFO, "Backend executed at PID=%i\n", bp->worker_pid); + return bRC_OK; +} + +/* + * Terminates the current backend pointed by backendctx context. + * The termination sequence consist of "End Job" protocol procedure and real + * backend process termination including communication channel close. + * When we'll get an error during "End Job" procedure then we inform the user + * and terminate the backend as usual without unnecessary formalities. :) + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * out: + * bRC_OK - when backend termination was successful, i.e. no error in + * "End Job" procedure + * bRC_Error - when backend termination encountered an error. + */ +bRC METAPLUGIN::terminate_current_backend(bpContext *ctx) +{ + bRC status = bRC_OK; + + if (send_endjob(ctx) != bRC_OK){ + /* error in end job */ + DMSG0(ctx, DERROR, "Error in EndJob.\n"); + status = bRC_Error; + } + int pid = backendctx->comm->get_backend_pid(); + DMSG(ctx, DINFO, "Terminate backend at PID=%d\n", pid) + backendctx->comm->terminate(ctx); + return status; +} + +/* + * Sends a "FINISH" command to all executed backends indicating the end of + * "Restore loop". + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * out: + * bRC_OK - when operation was successful + * bRC_Error - on any error + */ +bRC METAPLUGIN::signal_finish_all_backends(bpContext *ctx) +{ + bRC status = bRC_OK; + POOL_MEM cmd(PM_FNAME); + + if (backendlist != NULL){ + pm_strcpy(cmd, "FINISH\n"); + foreach_alist(backendctx, backendlist){ + if (backendctx->comm->write_command(ctx, cmd.addr()) < 0){ + status = bRC_Error; + } + if (!backendctx->comm->read_ack(ctx)){ + status = bRC_Error; + } + } + } + return status; +} + +/* + * Terminate all executed backends. + * Check METAPLUGIN::terminate_current_backend for more info. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * out: + * bRC_OK - when all backends termination was successful + * bRC_Error - when any backend termination encountered an error + */ +bRC METAPLUGIN::terminate_all_backends(bpContext *ctx) +{ + bRC status = bRC_OK; + + // for debug only + // dump_backendlist(ctx); + if (backendlist != NULL){ + foreach_alist(backendctx, backendlist){ + if (terminate_current_backend(ctx) != bRC_OK){ + status = bRC_Error; + } + } + } + return status; +} + +/* + * Send a "Job Info" protocol procedure parameters. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * type - a char compliant with the protocol indicating what jobtype we run + * out: + * bRC_OK - when send job info was successful + * bRC_Error - on any error + */ +bRC METAPLUGIN::send_jobinfo(bpContext *ctx, char type) +{ + int32_t rc; + int len; + bRC status = bRC_OK; + POOLMEM *cmd; + char lvl; + + cmd = get_pool_memory(PM_FNAME); + len = sizeof_pool_memory(cmd); + /* we will be sending Job Info data */ + bsnprintf(cmd, len, "Job\n"); + rc = backendctx->comm->write_command(ctx, cmd); + if (rc < 0){ + /* error */ + status = bRC_Error; + goto bailout; + } + /* required parameters */ + bsnprintf(cmd, len, "Name=%s\n", JobName); + rc = backendctx->comm->write_command(ctx, cmd); + if (rc < 0){ + /* error */ + status = bRC_Error; + goto bailout; + } + bsnprintf(cmd, len, "JobID=%i\n", JobId); + rc = backendctx->comm->write_command(ctx, cmd); + if (rc < 0){ + /* error */ + status = bRC_Error; + goto bailout; + } + bsnprintf(cmd, len, "Type=%c\n", type); + rc = backendctx->comm->write_command(ctx, cmd); + if (rc < 0){ + /* error */ + status = bRC_Error; + goto bailout; + } + /* optional parameters */ + if (mode != MODE::RESTORE){ + switch (mode){ + case MODE::BACKUP_FULL: + lvl = 'F'; + break; + case MODE::BACKUP_DIFF: + lvl = 'D'; + break; + case MODE::BACKUP_INCR: + lvl = 'I'; + break; + default: + lvl = 0; + } + if (lvl){ + bsnprintf(cmd, len, "Level=%c\n", lvl); + rc = backendctx->comm->write_command(ctx, cmd); + if (rc < 0){ + /* error */ + status = bRC_Error; + goto bailout; + } + } + } + if (since){ + bsnprintf(cmd, len, "Since=%ld\n", since); + rc = backendctx->comm->write_command(ctx, cmd); + if (rc < 0){ + /* error */ + status = bRC_Error; + goto bailout; + } + } + if (where){ + bsnprintf(cmd, len, "Where=%s\n", where); + rc = backendctx->comm->write_command(ctx, cmd); + if (rc < 0){ + /* error */ + status = bRC_Error; + goto bailout; + } + } + if (regexwhere){ + bsnprintf(cmd, len, "RegexWhere=%s\n", regexwhere); + rc = backendctx->comm->write_command(ctx, cmd); + if (rc < 0){ + /* error */ + status = bRC_Error; + goto bailout; + } + } + if (replace){ + bsnprintf(cmd, len, "Replace=%c\n", replace); + rc = backendctx->comm->write_command(ctx, cmd); + if (rc < 0){ + /* error */ + status = bRC_Error; + goto bailout; + } + } + backendctx->comm->signal_eod(ctx); + + if (!backendctx->comm->read_ack(ctx)){ + DMSG0(ctx, DERROR, "Wrong backend response to Job command.\n"); + JMSG0(ctx, backendctx->comm->is_fatal() ? M_FATAL : M_ERROR, "Wrong backend response to Job command.\n"); + status = bRC_Error; + } + +bailout: + free_pool_memory(cmd); + return status; +} + +/* + * Send a "Plugin Parameters" protocol procedure data. + * It parse plugin command and ini parameters before sending it to backend. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * command - a Plugin command for a job + * out: + * bRC_OK - when send parameters was successful + * bRC_Error - on any error + */ +bRC METAPLUGIN::send_parameters(bpContext *ctx, char *command) +{ + int32_t rc; + bRC status = bRC_OK; + POOL_MEM cmd(PM_FNAME); + alist params(16, not_owned_by_alist); + POOLMEM *param; + bool found; + +#ifdef DEVELOPER + static const char *regress_valid_params[] = + { + // add special test_backend commands to handle regression tests + "regress_error_plugin_params", + "regress_error_start_job", + "regress_error_backup_no_files", + "regress_error_backup_stderr", + "regress_error_estimate_stderr", + "regress_error_listing_stderr", + "regress_error_restore_stderr", + "regress_backup_plugin_objects", + "regress_backup_other_file", + "regress_error_backup_abort", + NULL, + }; +#endif + + /* parse and prepare final backend plugin params */ + status = parse_plugin_command(ctx, command, ¶ms); + if (status != bRC_OK){ + /* error */ + goto bailout; + } + + /* send backend info that parameters are coming */ + bsnprintf(cmd.c_str(), cmd.size(), "Params\n"); + rc = backendctx->comm->write_command(ctx, cmd.c_str()); + if (rc < 0){ + /* error */ + status = bRC_Error; + goto bailout; + } + /* send all prepared parameters */ + foreach_alist(param, ¶ms){ + // check valid parameter list + found = false; + for (int a = 0; valid_params[a] != NULL; a++ ) + { + DMSG3(ctx, DVDEBUG, "=> '%s' vs '%s' [%d]\n", param, valid_params[a], strlen(valid_params[a])); + if (strncasecmp(param, valid_params[a], strlen(valid_params[a])) == 0){ + found = true; + break; + } + } +#ifdef DEVELOPER + if (!found) + { + // now handle regression tests commands + for (int a = 0; regress_valid_params[a] != NULL; a++ ) + { + DMSG3(ctx, DVDEBUG, "regress=> '%s' vs '%s' [%d]\n", param, regress_valid_params[a], strlen(regress_valid_params[a])); + if (strncasecmp(param, regress_valid_params[a], strlen(regress_valid_params[a])) == 0){ + found = true; + break; + } + } + } +#endif + + // signal error if required + if (!found){ + pm_strcpy(cmd, param); + strip_trailing_junk(cmd.c_str()); + DMSG1(ctx, DERROR, "Unknown parameter %s in Plugin command.\n", cmd.c_str()); + JMSG1(ctx, M_ERROR, "Unknown parameter %s in Plugin command.\n", cmd.c_str()); + } + + rc = backendctx->comm->write_command(ctx, param); + if (rc < 0){ + /* error */ + status = bRC_Error; + goto bailout; + } + } + /* signal end of parameters block */ + backendctx->comm->signal_eod(ctx); + /* ack Params command */ + if (!backendctx->comm->read_ack(ctx)){ + DMSG0(ctx, DERROR, "Wrong backend response to Params command.\n"); + JMSG0(ctx, backendctx->comm->is_fatal() ? M_FATAL : M_ERROR, "Wrong backend response to Params command.\n"); + status = bRC_Error; + } + +bailout: + foreach_alist(param, ¶ms){ + free_and_null_pool_memory(param); + } + return status; +} + +/* + * Send start job command pointed by command variable. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * command - the command string to send + * out: + * bRC_OK - when send command was successful + * bRC_Error - on any error + */ +bRC METAPLUGIN::send_startjob(bpContext *ctx, const char *command) +{ + int32_t rc; + int len; + bRC status = bRC_OK; + POOLMEM *cmd; + + cmd = get_pool_memory(PM_FNAME); + len = sizeof_pool_memory(cmd); + + bsnprintf(cmd, len, command); + rc = backendctx->comm->write_command(ctx, cmd); + if (rc < 0){ + /* error */ + status = bRC_Error; + goto bailout; + } + + if (!backendctx->comm->read_ack(ctx)){ + DMSG(ctx, DERROR, "Wrong backend response to %s command.\n", command); + JMSG(ctx, backendctx->comm->is_fatal() ? M_FATAL : M_ERROR, "Wrong backend response to %s command.\n", command); + status = bRC_Error; + } + +bailout: + free_pool_memory(cmd); + return status; +} + +/* + * Send end job command to backend. + * It terminates the backend when command sending was unsuccessful, as it is + * the very last procedure in protocol. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * out: + * bRC_OK - when send command was successful + * bRC_Error - on any error + */ +bRC METAPLUGIN::send_endjob(bpContext *ctx) +{ + int32_t rc; + bRC status = bRC_OK; + POOL_MEM cmd(PM_FNAME); + + bsnprintf(cmd.c_str(), cmd.size(), "END\n"); + rc = backendctx->comm->write_command(ctx, cmd.c_str()); + if (rc < 0){ + /* error */ + status = bRC_Error; + } else { + if (!backendctx->comm->read_ack(ctx)){ + DMSG0(ctx, DERROR, "Wrong backend response to JobEnd command.\n"); + JMSG0(ctx, backendctx->comm->is_fatal() ? M_FATAL : M_ERROR, "Wrong backend response to JobEnd command.\n"); + status = bRC_Error; + } + backendctx->comm->signal_term(ctx); + } + return status; +} + +/* + * Send "BackupStart" protocol command. + * more info at METAPLUGIN::send_startjob + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * out: + * bRC_OK - when send command was successful + * bRC_Error - on any error + */ +bRC METAPLUGIN::send_startbackup(bpContext *ctx) +{ + return send_startjob(ctx, "BackupStart\n"); +} + +/* + * Send "EstimateStart" protocol command. + * more info at METAPLUGIN::send_startjob + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * out: + * bRC_OK - when send command was successful + * bRC_Error - on any error + */ +bRC METAPLUGIN::send_startestimate(bpContext *ctx) +{ + return send_startjob(ctx, "EstimateStart\n"); +} + +/* + * Send "ListingStart" protocol command. + * more info at METAPLUGIN::send_startjob + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * out: + * bRC_OK - when send command was successful + * bRC_Error - on any error + */ +bRC METAPLUGIN::send_startlisting(bpContext *ctx) +{ + return send_startjob(ctx, "ListingStart\n"); +} + +/* + * Send "RestoreStart" protocol command. + * more info at METAPLUGIN::send_startjob + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * out: + * bRC_OK - when send command was successful + * bRC_Error - on any error + */ +bRC METAPLUGIN::send_startrestore(bpContext *ctx) +{ + int32_t rc; + POOL_MEM cmd(PM_FNAME); + const char * command = "RestoreStart\n"; + POOL_MEM extpipename(PM_FNAME); + + pm_strcpy(cmd, command); + rc = backendctx->comm->write_command(ctx, cmd); + if (rc < 0){ + /* error */ + return bRC_Error; + } + + if (backendctx->comm->read_command(ctx, cmd) < 0){ + DMSG(ctx, DERROR, "Wrong backend response to %s command.\n", command); + JMSG(ctx, backendctx->comm->is_fatal() ? M_FATAL : M_ERROR, "Wrong backend response to %s command.\n", command); + return bRC_Error; + } + if (backendctx->comm->is_eod()){ + /* got EOD so the backend is ready for restore */ + return bRC_OK; + } + + /* here we expect a PIPE: command only */ + if (scan_parameter_str(cmd, "PIPE:", extpipename)){ + /* got PIPE: */ + DMSG(ctx, DINFO, "PIPE:%s\n", extpipename); + backendctx->comm->set_extpipename(extpipename.c_str()); + /* TODO: decide if plugin should verify if extpipe is available */ + pm_strcpy(cmd, "OK\n"); + rc = backendctx->comm->write_command(ctx, cmd); + if (rc < 0){ + /* error */ + return bRC_Error; + } + return bRC_OK; + } + return bRC_Error; +} + +/* + * Allocate and initialize new backend contexts list at backendlist. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * command - a Plugin command for a job as a first backend context + * out: + * New backend contexts list at backendlist allocated and initialized. + * The only error we can get here is out of memory error, handled internally + * by Bacula itself. + */ +void METAPLUGIN::new_backendlist(bpContext *ctx, char *command) +{ + /* we assumed 8 backends at start, should be sufficient */ + backendlist = New(alist(8, not_owned_by_alist)); + /* our first backend */ + backendctx = New(BackendCTX(command)); + /* add backend context to our list */ + backendlist->append(backendctx); + DMSG0(ctx, DINFO, "First backend allocated.\n"); +} + +/* + * Switches current backend context or executes new one when required. + * The backend (application path) to execute is set in BACKEND_CMD compile + * variable and handled by METAPLUGIN::run_backend() method. Just before new + * backend execution the method search for already spawned backends which + * handles the same Plugin command and when found the current backend context + * is switched to already available on list. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * command - the Plugin command for which we execute a backend + * out: + * bRC_OK - success and backendctx has a current backend context and Plugin + * should send an initialization procedure + * bRC_Max - when was already prepared and initialized, so no + * reinitialization required + * bRC_Error - error in switching or running the backend + */ +bRC METAPLUGIN::switch_or_run_backend(bpContext *ctx, char *command) +{ + bool found = false; + BackendCTX *pini; + + DMSG0(ctx, DINFO, "Switch or run Backend.\n"); + // for debug only + // dump_backendlist(ctx); + if (backendlist == NULL){ + /* new backend list required */ + new_backendlist(ctx, command); + } else { + foreach_alist(pini, backendlist){ + // DMSG(ctx, DINFO, "bctx: %p\n", pini); + if (strcmp(pini->cmd, command) == 0){ + found = true; + backendctx = pini; + break; + } + } + // for debug only + // dump_backendlist(ctx); + if (!found){ + /* it is a new backend, so we have to allocate the context for it */ + backendctx = New(BackendCTX(command)); + /* add backend context to our list */ + backendlist->append(backendctx); + DMSG0(ctx, DINFO, "New backend allocated.\n"); + } else { + /* we have the backend with the same command already */ + if (backendctx->comm->is_open()){ + /* and its open, so skip running the new */ + DMSG0(ctx, DINFO, "Backend already prepared.\n"); + return bRC_Max; + } else { + DMSG0(ctx, DINFO, "Backend context available, but backend not prepared.\n"); + } + } + } + // for debug only + // dump_backendlist(ctx); + if (run_backend(ctx) != bRC_OK){ + return bRC_Error; + } + return bRC_OK; +} + +/* + * Prepares the backend for Backup/Restore/Estimate loops. + * The preparation consist of backend execution and initialization, required + * protocol initialize procedures: "Handshake", "Job Info", + * "Plugin Parameters" and "Start Backup"/"Start Restore"/"Start Estimate" + * depends on that job it is. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * command - a Plugin command from FileSet to start the job + * out: + * bRC_OK - when backend is operational and prepared for job or when it is + * not our plugin command, anyway a success + * bRC_Error - encountered any error during preparation + */ +bRC METAPLUGIN::prepare_backend(bpContext *ctx, char type, char *command) +{ + bRC status; + + /* check if it is our Plugin command */ + if (strncmp(PLUGINPREFIX, command, strlen(PLUGINPREFIX)) != 0){ + /* it is not our plugin prefix */ + return bRC_OK; + } + + /* switch backend context, so backendctx has all required variables available */ + status = switch_or_run_backend(ctx, command); + if (status == bRC_Max){ + /* already prepared, skip rest of preparation */ + return bRC_OK; + } + if (status != bRC_OK){ + /* we have some error here */ + return bRC_Error; + } + /* handshake (1) */ + DMSG0(ctx, DINFO, "Backend handshake...\n"); + if (!backendctx->comm->handshake(ctx, PLUGINNAME, PLUGINAPI)) + { + backendctx->comm->terminate(ctx); + return bRC_Error; + } + /* Job Info (2) */ + DMSG0(ctx, DINFO, "Job Info (2) ...\n"); + if (send_jobinfo(ctx, type) != bRC_OK){ + backendctx->comm->terminate(ctx); + return bRC_Error; + } + /* Plugin Params (3) */ + DMSG0(ctx, DINFO, "Plugin Params (3) ...\n"); + if (send_parameters(ctx, command) != bRC_OK){ + backendctx->comm->terminate(ctx); + return bRC_Error; + } + switch (type){ + case BACKEND_JOB_INFO_BACKUP: + /* Start Backup (4) */ + DMSG0(ctx, DINFO, "Start Backup (4) ...\n"); + if (send_startbackup(ctx) != bRC_OK){ + backendctx->comm->terminate(ctx); + return bRC_Error; + } + break; + case BACKEND_JOB_INFO_ESTIMATE: + /* Start Estimate or Listing (4) */ + if (listing){ + DMSG0(ctx, DINFO, "Start Listing (4) ...\n"); + if (send_startlisting(ctx) != bRC_OK){ + backendctx->comm->terminate(ctx); + return bRC_Error; + } + } else { + DMSG0(ctx, DINFO, "Start Estimate (4) ...\n"); + if (send_startestimate(ctx) != bRC_OK){ + backendctx->comm->terminate(ctx); + return bRC_Error; + } + } + break; + case BACKEND_JOB_INFO_RESTORE: + /* Start Restore (4) */ + DMSG0(ctx, DINFO, "Start Restore (4) ...\n"); + if (send_startrestore(ctx) != bRC_OK){ + backendctx->comm->terminate(ctx); + return bRC_Error; + } + break; + default: + return bRC_Error; + } + DMSG0(ctx, DINFO, "Prepare backend done.\n"); + return bRC_OK; +} + +/* + * This is the main method for handling events generated by Bacula. + * The behavior of the method depends on event type generated, but there are + * some events which does nothing, just return with bRC_OK. Every event is + * tracked in debug trace file to verify the event flow during development. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * event - a Bacula event structure + * value - optional event value + * out: + * bRC_OK - in most cases signal success/no error + * bRC_Error - in most cases signal error + * - depend on Bacula Plugin API if applied + */ +bRC METAPLUGIN::handlePluginEvent(bpContext *ctx, bEvent *event, void *value) +{ + bRC status; + POOL_MEM tmp; + + switch (event->eventType) { + case bEventJobStart: + DMSG(ctx, D3, "bEventJobStart value=%s\n", NPRT((char *)value)); + getBaculaVar(bVarJobId, (void *)&JobId); + getBaculaVar(bVarJobName, (void *)&JobName); + break; + + case bEventJobEnd: + status = bRC_OK; + DMSG(ctx, D3, "bEventJobEnd value=%s\n", NPRT((char *)value)); + status = terminate_all_backends(ctx); + return status; + + case bEventLevel: + char lvl; + lvl = (char)((intptr_t) value & 0xff); + DMSG(ctx, D2, "bEventLevel='%c'\n", lvl); + switch (lvl) { + case 'F': + DMSG0(ctx, D2, "backup level = Full\n"); + mode = MODE::BACKUP_FULL; + break; + case 'I': + DMSG0(ctx, D2, "backup level = Incr\n"); + mode = MODE::BACKUP_INCR; + break; + case 'D': + DMSG0(ctx, D2, "backup level = Diff\n"); + mode = MODE::BACKUP_DIFF; + break; + default: + DMSG0(ctx, D2, "unsupported backup level!\n"); + return bRC_Error; + } + break; + + case bEventSince: + since = (time_t) value; + DMSG(ctx, D2, "bEventSince=%ld\n", (intptr_t) since); + break; + + case bEventStartBackupJob: + DMSG(ctx, D3, "bEventStartBackupJob value=%s\n", NPRT((char *)value)); + break; + + case bEventEndBackupJob: + DMSG(ctx, D2, "bEventEndBackupJob value=%s\n", NPRT((char *)value)); + break; + + case bEventStartRestoreJob: + DMSG(ctx, DINFO, "StartRestoreJob value=%s\n", NPRT((char *)value)); + getBaculaVar(bVarWhere, &where); + DMSG(ctx, DINFO, "Where=%s\n", NPRT(where)); + getBaculaVar(bVarRegexWhere, ®exwhere); + DMSG(ctx, DINFO, "RegexWhere=%s\n", NPRT(regexwhere)); + getBaculaVar(bVarReplace, &replace); + DMSG(ctx, DINFO, "Replace=%c\n", replace); + mode = MODE::RESTORE; + break; + + case bEventEndRestoreJob: + DMSG(ctx, DINFO, "bEventEndRestoreJob value=%s\n", NPRT((char *)value)); + return signal_finish_all_backends(ctx); + + /* Plugin command e.g. plugin = :parameters */ + case bEventEstimateCommand: + DMSG(ctx, D1, "bEventEstimateCommand value=%s\n", NPRT((char *)value)); + estimate = true; + return prepare_backend(ctx, BACKEND_JOB_INFO_ESTIMATE, (char*)value); + + /* Plugin command e.g. plugin = :parameters */ + case bEventBackupCommand: + DMSG(ctx, D2, "bEventBackupCommand value=%s\n", NPRT((char *)value)); + robjsent = false; + return prepare_backend(ctx, BACKEND_JOB_INFO_BACKUP, (char*)value); + + /* Plugin command e.g. plugin = :parameters */ + case bEventRestoreCommand: + DMSG(ctx, D2, "bEventRestoreCommand value=%s\n", NPRT((char *)value)); + return prepare_backend(ctx, BACKEND_JOB_INFO_RESTORE, (char*)value); + + /* Plugin command e.g. plugin = :parameters */ + case bEventPluginCommand: + DMSG(ctx, D2, "bEventPluginCommand value=%s\n", NPRT((char *)value)); + break; + + case bEventOptionPlugin: + case bEventHandleBackupFile: + if (isourplugincommand(PLUGINPREFIX, (char*)value)){ + DMSG0(ctx, DERROR, "Invalid handle Option Plugin called!\n"); + JMSG2(ctx, M_FATAL, + "The %s plugin doesn't support the Option Plugin configuration.\n" + "Please review your FileSet and move the Plugin=%s" + "... command into the Include {} block.\n", + PLUGINNAME, PLUGINPREFIX); + return bRC_Error; + } + break; + + case bEventEndFileSet: + DMSG(ctx, D3, "bEventEndFileSet value=%s\n", NPRT((char *)value)); + break; + + case bEventRestoreObject: + /* Restore Object handle - a plugin configuration for restore and user supplied parameters */ + if (!value){ + DMSG0(ctx, DINFO, "End restore objects\n"); + break; + } + DMSG(ctx, D2, "bEventRestoreObject value=%p\n", value); + return parse_plugin_restoreobj(ctx, (restore_object_pkt *) value); + + case bEventCancelCommand: + DMSG(ctx, D3, "bEventCancelCommand value=%s\n", NPRT((char *)value)); + /* + TODO: The code can set a flag (better if protected via a global mutex), and we + can send a kill USR1 or TERM signal to the backend if the variable is + protected with the global mutex and available easily. + + PETITION: Our plugin (RHV WhiteBearSolutions) search the packet E CANCEL. If you modify this behaviour, please you notify us. + */ + bsscanf("CANCEL", "%s", tmp.c_str()); + backendctx->comm->signal_error(ctx, tmp.c_str()); + break; + + default: + // enabled only for Debug + DMSG2(ctx, D2, "Unknown event: %s (%d) \n", eventtype2str(event), event->eventType); + } + + return bRC_OK; +} + +/* + * Make a real data read from the backend and checks for EOD. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * io - Bacula Plugin API I/O structure for I/O operations + * out: + * bRC_OK - when successful + * bRC_Error - on any error + */ +bRC METAPLUGIN::perform_read_data(bpContext *ctx, struct io_pkt *io) +{ + int rc; + + if (nodata){ + io->status = 0; + return bRC_OK; + } + rc = backendctx->comm->read_data_fixed(ctx, io->buf, io->count); + if (rc < 0){ + io->status = rc; + io->io_errno = rc; + return bRC_Error; + } + io->status = rc; + if (backendctx->comm->is_eod()){ + /* TODO: we signal EOD as rc=0, so no need to explicity check for EOD, right? */ + io->status = 0; + } + return bRC_OK; +} + +/* + * Make a real write data to the backend. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * io - Bacula Plugin API I/O structure for I/O operations + * out: + * bRC_OK - when successful + * bRC_Error - on any error + */ +bRC METAPLUGIN::perform_write_data(bpContext *ctx, struct io_pkt *io) +{ + int rc; + POOL_MEM cmd(PM_FNAME); + + /* check if DATA was sent */ + if (nodata){ + pm_strcpy(cmd, "DATA\n"); + rc = backendctx->comm->write_command(ctx, cmd.c_str()); + if (rc < 0){ + /* error */ + io->status = rc; + io->io_errno = rc; + return bRC_Error; + } + /* DATA command sent */ + nodata = false; + } + DMSG1(ctx, DVDEBUG, "perform_write_data: %d\n", io->count); + rc = backendctx->comm->write_data(ctx, io->buf, io->count); + io->status = rc; + if (rc < 0){ + io->io_errno = rc; + return bRC_Error; + } + nodata = false; + return bRC_OK; +} + +/* + * Handle protocol "DATA" command from backend during backup loop. + * It handles a "no data" flag when saved file contains no data to + * backup (empty file). + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * io - Bacula Plugin API I/O structure for I/O operations + * out: + * bRC_OK - when successful + * bRC_Error - on any error + * io->status, io->io_errno - set to error on any error + */ +bRC METAPLUGIN::perform_backup_open(bpContext *ctx, struct io_pkt *io) +{ + int rc; + POOL_MEM cmd(PM_FNAME); + + /* expecting DATA command */ + nodata = false; + rc = backendctx->comm->read_command(ctx, cmd); + if (backendctx->comm->is_eod()){ + /* no data for file */ + nodata = true; + } else + // expect no error and 'DATA' starting packet + if (rc < 0 || !bstrcmp(cmd.c_str(), "DATA")){ + io->status = rc; + io->io_errno = EIO; + openerror = backendctx->comm->is_fatal() ? false : true; + return bRC_Error; + } + return bRC_OK; +} + +/* + * Signal the end of data to restore and verify acknowledge from backend. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * io - Bacula Plugin API I/O structure for I/O operations + * out: + * bRC_OK - when successful + * bRC_Error - on any error + */ +bRC METAPLUGIN::perform_write_end(bpContext *ctx, struct io_pkt *io) +{ + if (!nodata){ + /* signal end of data to restore and get ack */ + if (!backendctx->comm->send_ack(ctx)){ + io->status = -1; + io->io_errno = EPIPE; + return bRC_Error; + } + } + return bRC_OK; +} + +/* + * Reads ACL data from backend during backup. Save it for handleXACLdata from + * Bacula. As we do not know if a backend will send the ACL data or not, we + * cannot wait to read this data until handleXACLdata will be called. + * TODO: The method has a limitation and accept up to PM_BSOCK acl data to save + * which is about 64kB. It should be sufficient for virtually all acl data + * we can imagine. But when a backend developer could expect a larger data + * to save he should rewrite perform_read_acl() method. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * out: + * bRC_OK - when ACL data was read successfully and this.readacl set to true + * bRC_Error - on any error during acl data read + */ +bRC METAPLUGIN::perform_read_acl(bpContext *ctx) +{ + DMSG0(ctx, DINFO, "perform_read_acl\n"); + acldatalen = backendctx->comm->read_data(ctx, acldata); + if (acldatalen < 0){ + DMSG0(ctx, DERROR, "Cannot read ACL data from backend.\n"); + return bRC_Error; + } + DMSG1(ctx, DINFO, "readACL: %i\n", acldatalen); + if (!backendctx->comm->read_ack(ctx)){ + /* should get EOD */ + DMSG0(ctx, DERROR, "Protocol error, should get EOD.\n"); + return bRC_Error; + } + readacl = true; + return bRC_OK; +} + +/* + * Reads XATTR data from backend during backup. Save it for handleXACLdata from + * Bacula. As we do not know if a backend will send the XATTR data or not, we + * cannot wait to read this data until handleXACLdata will be called. + * TODO: The method has a limitation and accept up to PM_BSOCK xattr data to save + * which is about 64kB. It should be sufficient for virtually all xattr data + * we can imagine. But when a backend developer could expect a larger data + * to save he should rewrite perform_read_xattr() method. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * out: + * bRC_OK - when XATTR data was read successfully and this.readxattr set + * to true + * bRC_Error - on any error during acl data read + */ +bRC METAPLUGIN::perform_read_xattr(bpContext *ctx) +{ + DMSG0(ctx, DINFO, "perform_read_xattr\n"); + xattrdatalen = backendctx->comm->read_data(ctx, xattrdata); + if (xattrdatalen < 0){ + DMSG0(ctx, DERROR, "Cannot read XATTR data from backend.\n"); + return bRC_Error; + } + DMSG1(ctx, DINFO, "readXATTR: %i\n", xattrdatalen); + if (!backendctx->comm->read_ack(ctx)){ + /* should get EOD */ + DMSG0(ctx, DERROR, "Protocol error, should get EOD.\n"); + return bRC_Error; + } + readxattr = true; + return bRC_OK; +} + +/* + * Sends ACL data from restore stream to backend. + * TODO: The method has a limitation and accept a single xacl_pkt call for + * a single file. As the restored acl stream and records are the same as + * was saved during backup, you can expect no more then a single PM_BSOCK + * and about 64kB of acl data send to backend. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * xacl_pkt - the restored ACL data for backend + * out: + * bRC_OK - when ACL data was restored successfully + * bRC_Error - on any error during acl data restore + */ +bRC METAPLUGIN::perform_write_acl(bpContext* ctx, xacl_pkt* xacl) +{ + int rc; + POOL_MEM cmd(PM_FNAME); + + if (xacl->count > 0){ + /* send command ACL */ + pm_strcpy(cmd, "ACL\n"); + backendctx->comm->write_command(ctx, cmd.c_str()); + /* send acls data */ + DMSG1(ctx, DINFO, "writeACL: %i\n", xacl->count); + rc = backendctx->comm->write_data(ctx, xacl->content, xacl->count); + if (rc < 0){ + /* got some error */ + return bRC_Error; + } + /* signal end of acls data to restore and get ack */ + if (!backendctx->comm->send_ack(ctx)){ + return bRC_Error; + } + } + return bRC_OK; +} + +/* + * Sends XATTR data from restore stream to backend. + * TODO: The method has a limitation and accept a single xacl_pkt call for + * a single file. As the restored acl stream and records are the same as + * was saved during backup, you can expect no more then a single PM_BSOCK + * and about 64kB of acl data send to backend. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * xacl_pkt - the restored XATTR data for backend + * out: + * bRC_OK - when XATTR data was restored successfully + * bRC_Error - on any error during acl data restore + */ +bRC METAPLUGIN::perform_write_xattr(bpContext* ctx, xacl_pkt* xacl) +{ + int rc; + POOL_MEM cmd(PM_FNAME); + + if (xacl->count > 0){ + /* send command XATTR */ + pm_strcpy(cmd, "XATTR\n"); + backendctx->comm->write_command(ctx, cmd.c_str()); + /* send xattrs data */ + DMSG1(ctx, DINFO, "writeXATTR: %i\n", xacl->count); + rc = backendctx->comm->write_data(ctx, xacl->content, xacl->count); + if (rc < 0){ + /* got some error */ + return bRC_Error; + } + /* signal end of xattrs data to restore and get ack */ + if (!backendctx->comm->send_ack(ctx)){ + return bRC_Error; + } + } + return bRC_OK; +} + +/* + * The method works as a dispatcher for expected commands received from backend. + * It handles a three commands associated with file attributes/metadata: + * - FNAME:... - the next file to backup + * - ACL - next data will be acl data, so perform_read_acl() + * - XATTR - next data will be xattr data, so perform_read_xattr() + * and additionally when no more files to backup it handles EOD. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * out: + * bRC_OK - when plugin read the command, dispatched a work and setup flags + * bRC_Error - on any error during backup + */ +bRC METAPLUGIN::perform_read_metadata(bpContext *ctx) +{ + POOL_MEM cmd(PM_FNAME); + + DMSG0(ctx, DDEBUG, "perform_read_metadata()\n"); + // setup flags + nextfile = pluginobject = readacl = readxattr = false; + // loop on metadata from backend or EOD which means no more files to backup + while (true) + { + if (backendctx->comm->read_command(ctx, cmd) > 0){ + /* yup, should read FNAME, ACL or XATTR from backend, check which one */ + DMSG(ctx, DDEBUG, "read_command(1): %s\n", cmd.c_str()); + if (scan_parameter_str(cmd, "FNAME:", fname)) + { + /* got FNAME: */ + nextfile = true; + return bRC_OK; + } + if (scan_parameter_str(cmd, "PLUGINOBJ:", fname)) + { + /* got Plugin Object header */ + nextfile = true; + pluginobject = true; + return bRC_OK; + } + if (bstrcmp(cmd.c_str(), "ACL")){ + /* got ACL header */ + perform_read_acl(ctx); + continue; + } + if (bstrcmp(cmd.c_str(), "XATTR")){ + /* got XATTR header */ + perform_read_xattr(ctx); + continue; + } + /* error in protocol */ + DMSG(ctx, DERROR, "Protocol error, got unknown command: %s\n", cmd.c_str()); + JMSG(ctx, M_FATAL, "Protocol error, got unknown command: %s\n", cmd.c_str()); + return bRC_Error; + } else { + if (backendctx->comm->is_fatal()){ + /* raise up error from backend */ + return bRC_Error; + } + if (backendctx->comm->is_eod()){ + /* no more files to backup */ + DMSG0(ctx, DDEBUG, "No more files to backup from backend.\n"); + return bRC_OK; + } + } + } + + return bRC_Error; +} + +/** + * @brief + * + * @param ctx + * @param sp + * @return bRC + */ +bRC METAPLUGIN::perform_read_pluginobject(bpContext *ctx, struct save_pkt *sp) +{ + POOL_MEM cmd(PM_FNAME); + + if (strlen(fname.c_str()) == 0) + { + // input variable is not valid + return bRC_Error; + } + + sp->plugin_obj.path = fname.c_str(); + DMSG0(ctx, DDEBUG, "perform_read_pluginobject()\n"); + // loop on plugin objects parameters from backend and EOD + while (true) + { + if (backendctx->comm->read_command(ctx, cmd) > 0) + { + DMSG(ctx, DDEBUG, "read_command(1): %s\n", cmd.c_str()); + if (scan_parameter_str(cmd, "PLUGINOBJ_CAT:", plugin_obj_cat)){ + DMSG1(ctx, DDEBUG, "category: %s\n", plugin_obj_cat.c_str()); + sp->plugin_obj.object_category = plugin_obj_cat.c_str(); + continue; + } + if (scan_parameter_str(cmd, "PLUGINOBJ_TYPE:", plugin_obj_type)) + { + DMSG1(ctx, DDEBUG, "type: %s\n", plugin_obj_type.c_str()); + sp->plugin_obj.object_type = plugin_obj_type.c_str(); + continue; + } + if (scan_parameter_str(cmd, "PLUGINOBJ_NAME:", plugin_obj_name)) + { + DMSG1(ctx, DDEBUG, "name: %s\n", plugin_obj_name.c_str()); + sp->plugin_obj.object_name = plugin_obj_name.c_str(); + continue; + } + if (scan_parameter_str(cmd, "PLUGINOBJ_SRC:", plugin_obj_src)) + { + DMSG1(ctx, DDEBUG, "src: %s\n", plugin_obj_src.c_str()); + sp->plugin_obj.object_source = plugin_obj_src.c_str(); + continue; + } + if (scan_parameter_str(cmd, "PLUGINOBJ_UUID:", plugin_obj_uuid)) + { + DMSG1(ctx, DDEBUG, "uuid: %s\n", plugin_obj_uuid.c_str()); + sp->plugin_obj.object_uuid = plugin_obj_uuid.c_str(); + continue; + } + POOL_MEM param(PM_NAME); + if (scan_parameter_str(cmd, "PLUGINOBJ_SIZE:", param)) + { + if (!size_to_uint64(param.c_str(), strlen(param.c_str()), &plugin_obj_size)){ + // error in convert + DMSG1(ctx, DERROR, "Cannot convert Plugin Object Size to integer! p=%s\n", param.c_str()); + JMSG1(ctx, M_ERROR, "Cannot convert Plugin Object Size to integer! p=%s\n", param.c_str()); + return bRC_Error; + } + DMSG1(ctx, DDEBUG, "size: %llu\n", plugin_obj_size); + sp->plugin_obj.object_size = plugin_obj_size; + continue; + } + /* error in protocol */ + DMSG(ctx, DERROR, "Protocol error, got unknown command: %s\n", cmd.c_str()); + JMSG(ctx, M_FATAL, "Protocol error, got unknown command: %s\n", cmd.c_str()); + return bRC_Error; + } else { + if (backendctx->comm->is_fatal()){ + /* raise up error from backend */ + return bRC_Error; + } + if (backendctx->comm->is_eod()){ + /* no more plugin object params to backup */ + DMSG0(ctx, DINFO, "No more Plugin Object params from backend.\n"); + pluginobject = false; + return bRC_OK; + } + } + } + + return bRC_Error; + +} + +/* + * Handle Bacula Plugin I/O API for backend + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * io - Bacula Plugin API I/O structure for I/O operations + * out: + * bRC_OK - when successful + * bRC_Error - on any error + * io->status, io->io_errno - correspond to a plugin io operation status + */ +bRC METAPLUGIN::pluginIO(bpContext *ctx, struct io_pkt *io) +{ + static int rw = 0; // this variable handles single debug message + + /* assume no error from the very beginning */ + io->status = 0; + io->io_errno = 0; + switch (io->func) { + case IO_OPEN: + DMSG(ctx, D2, "IO_OPEN: (%s)\n", io->fname); + switch (mode){ + case MODE::BACKUP_FULL: + case MODE::BACKUP_INCR: + case MODE::BACKUP_DIFF: + return perform_backup_open(ctx, io); + case MODE::RESTORE: + nodata = true; + break; + default: + return bRC_Error; + } + break; + case IO_READ: + if (!rw) { + rw = 1; + DMSG2(ctx, D2, "IO_READ buf=%p len=%d\n", io->buf, io->count); + } + switch (mode){ + case MODE::BACKUP_FULL: + case MODE::BACKUP_INCR: + case MODE::BACKUP_DIFF: + return perform_read_data(ctx, io); + default: + return bRC_Error; + } + break; + case IO_WRITE: + if (!rw) { + rw = 1; + DMSG2(ctx, D2, "IO_WRITE buf=%p len=%d\n", io->buf, io->count); + } + switch (mode){ + case MODE::RESTORE: + return perform_write_data(ctx, io); + default: + return bRC_Error; + } + break; + case IO_CLOSE: + DMSG0(ctx, D2, "IO_CLOSE\n"); + rw = 0; + if (!backendctx->comm->close_extpipe(ctx)){ + return bRC_Error; + } + switch (mode){ + case MODE::RESTORE: + return perform_write_end(ctx, io); + case MODE::BACKUP_FULL: + case MODE::BACKUP_INCR: + case MODE::BACKUP_DIFF: + return perform_read_metadata(ctx); + default: + return bRC_Error; + } + break; + } + + return bRC_OK; +} + +/* + * Unimplemented, always return bRC_OK. + */ +bRC METAPLUGIN::getPluginValue(bpContext *ctx, pVariable var, void *value) +{ + return bRC_OK; +} + +/* + * Unimplemented, always return bRC_OK. + */ +bRC METAPLUGIN::setPluginValue(bpContext *ctx, pVariable var, void *value) +{ + return bRC_OK; +} + +/* + * Get all required information from backend to populate save_pkt for Bacula. + * It handles a Restore Object (FT_PLUGIN_CONFIG) for every Full backup and + * new Plugin Backup Command if setup in FileSet. It uses a help from + * endBackupFile() handling the next FNAME command for the next file to + * backup. The communication protocol requires some file attributes command + * required it raise the error when insufficient parameters received from + * backend. It assumes some parameters at save_pkt struct to be automatically + * set like: sp->portable, sp->statp.st_blksize, sp->statp.st_blocks. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * save_pkt - Bacula Plugin API save packet structure + * out: + * bRC_OK - when save_pkt prepared successfully and we have file to backup + * bRC_Max - when no more files to backup + * bRC_Error - in any error + */ +bRC METAPLUGIN::startBackupFile(bpContext *ctx, struct save_pkt *sp) +{ + POOL_MEM cmd(PM_FNAME); + POOL_MEM tmp(PM_FNAME); + char type; + size_t size; + int uid, gid; + uint perms; + int nlinks; + int reqparams = 2; + + /* The first file in Full backup, is the RestoreObject */ + if (!estimate && mode == MODE::BACKUP_FULL && robjsent == false) { + ConfigFile ini; + + /* robj for the first time, allocate the buffer */ + if (!robjbuf){ + robjbuf = get_pool_memory(PM_FNAME); + } + + ini.register_items(plugin_items_dump, sizeof(struct ini_items)); + sp->restore_obj.object_name = (char *)INI_RESTORE_OBJECT_NAME; + sp->restore_obj.object_len = ini.serialize(&robjbuf); + sp->restore_obj.object = robjbuf; + sp->type = FT_PLUGIN_CONFIG; + DMSG0(ctx, DINFO, "Prepared RestoreObject sent.\n"); + return bRC_OK; + } + + // check if this is the first file from backend to backup + if (!nextfile){ + // so read FNAME or EOD/Error + if (perform_read_metadata(ctx) != bRC_OK){ + // signal error + return bRC_Error; + } + if (!nextfile){ + // got EOD, so no files to backup at all! + // if we return a value different from bRC_OK then Bacula will finish + // backup process, which at first call means no files to archive + return bRC_Max; + } + } + // setup required fname in save_pkt + DMSG(ctx, DINFO, "fname:%s\n", fname.c_str()); + sp->fname = fname.c_str(); + + if (!pluginobject){ + // here we handle standard metadata + reqparams--; + + /* we require lname */ + if (!lname){ + lname = get_pool_memory(PM_FNAME); + } + + while (backendctx->comm->read_command(ctx, cmd) > 0) + { + DMSG(ctx, DINFO, "read_command(2): %s\n", cmd.c_str()); + if (sscanf(cmd.c_str(), "STAT:%c %ld %d %d %o %d", &type, &size, &uid, &gid, &perms, &nlinks) == 6){ + sp->statp.st_size = size; + sp->statp.st_nlink = nlinks; + sp->statp.st_uid = uid; + sp->statp.st_gid = gid; + sp->statp.st_mode = perms; + switch (type){ + case 'F': + sp->type = FT_REG; + break; + case 'E': + sp->type = FT_REGE; + break; + case 'D': + sp->type = FT_DIREND; + sp->link = sp->fname; + break; + case 'S': + sp->type = FT_LNK; + reqparams++; + break; + default: + /* we need to signal error */ + sp->type = FT_REG; + DMSG2(ctx, DERROR, "Invalid file type: %c for %s\n", type, fname); + JMSG2(ctx, M_ERROR, "Invalid file type: %c for %s\n", type, fname); + } + DMSG6(ctx, DINFO, "STAT:%c size:%lld uid:%d gid:%d mode:%06o nl:%d\n", type, size, uid, gid, perms, nlinks); + reqparams--; + continue; + } + if (bsscanf(cmd.c_str(), "TSTAMP:%ld %ld %ld", &sp->statp.st_atime, &sp->statp.st_mtime, &sp->statp.st_ctime) == 3) + { + DMSG3(ctx, DINFO, "TSTAMP:%ld(at) %ld(mt) %ld(ct)\n", sp->statp.st_atime, sp->statp.st_mtime, sp->statp.st_ctime); + continue; + } + if (bsscanf(cmd.c_str(), "LSTAT:%s", lname) == 1) + { + sp->link = lname; + reqparams--; + DMSG(ctx, DINFO, "LSTAT:%s\n", lname); + continue; + } + if (scan_parameter_str(cmd, "PIPE:", tmp)){ + /* handle PIPE command */ + DMSG(ctx, DINFO, "read pipe at: %s\n", tmp.c_str()); + int extpipe = open(tmp.c_str(), O_RDONLY); + if (extpipe > 0) + { + DMSG0(ctx, DINFO, "ExtPIPE file available.\n"); + backendctx->comm->set_extpipe(extpipe); + pm_strcpy(tmp, "OK\n"); + backendctx->comm->write_command(ctx, tmp.c_str()); + continue; + } else { + /* here are common error signaling */ + berrno be; + DMSG(ctx, DERROR, "ExtPIPE file open error! Err=%s\n", be.bstrerror()); + JMSG(ctx, backendctx->comm->is_abort_on_error() ? M_FATAL : M_ERROR, "ExtPIPE file open error! Err=%s\n", be.bstrerror()); + pm_strcpy(tmp, "Err\n"); + backendctx->comm->signal_error(ctx, tmp.c_str()); + return bRC_Error; + } + } + } + + DMSG0(ctx, DINFO, "File attributes end.\n"); + if (reqparams > 0) + { + DMSG0(ctx, DERROR, "Protocol error, not enough file attributes from backend.\n"); + JMSG0(ctx, M_FATAL, "Protocol error, not enough file attributes from backend.\n"); + return bRC_Error; + } + + } else { + // handle Plugin Object parameters + if (perform_read_pluginobject(ctx, sp) != bRC_OK) + { + // signal error + return bRC_Error; + } + sp->type = FT_PLUGIN_OBJECT; + size = sp->plugin_obj.object_size; + } + + if (backendctx->comm->is_error()){ + return bRC_Error; + } + + sp->portable = true; + sp->statp.st_blksize = 4096; + sp->statp.st_blocks = size / 4096 + 1; + + return bRC_OK; +} + +/* + * Check for a next file to backup or the end of the backup loop. + * The next file to backup is indicated by a FNAME command from backend and + * no more files to backup as EOD. It helps startBackupFile handling FNAME + * for next file. + * + * in: + * bpContext - for Bacula debug and jobinfo messages + * save_pkt - Bacula Plugin API save packet structure + * out: + * bRC_OK - when no more files to backup + * bRC_More - when Bacula should expect a next file + * bRC_Error - in any error + */ +bRC METAPLUGIN::endBackupFile(bpContext *ctx) +{ + POOL_MEM cmd(PM_FNAME); + + if (!estimate){ + /* The current file was the restore object, so just ask for the next file */ + if (mode == MODE::BACKUP_FULL && robjsent == false) { + robjsent = true; + return bRC_More; + } + } + + // check for next file onnly when no previous error + if (!openerror) + { + if (estimate) + { + if (perform_read_metadata(ctx) != bRC_OK) + { + /* signal error */ + return bRC_Error; + } + } + + if (nextfile) + { + DMSG1(ctx, DINFO, "nextfile %s backup!\n", fname.c_str()); + return bRC_More; + } + } + + return bRC_OK; +} + +/* + * The PLUGIN is not using this callback to handle restore. + */ +bRC METAPLUGIN::startRestoreFile(bpContext *ctx, const char *cmd) +{ + return bRC_OK; +} + +/* + * The PLUGIN is not using this callback to handle restore. + */ +bRC METAPLUGIN::endRestoreFile(bpContext *ctx) +{ + return bRC_OK; +} + +/* + * Prepares a file to restore attributes based on data from restore_pkt. + * It handles a response from backend to show if + * + * in: + * bpContext - bacula plugin context + * restore_pkt - Bacula Plugin API restore packet structure + * out: + * bRC_OK - when success reported from backend + * rp->create_status = CF_EXTRACT - the backend will restore the file + * with pleasure + * rp->create_status = CF_SKIP - the backend wants to skip restoration, i.e. + * the file already exist and Replace=n was set + * bRC_Error, rp->create_status = CF_ERROR - in any error + */ +bRC METAPLUGIN::createFile(bpContext *ctx, struct restore_pkt *rp) +{ + POOL_MEM cmd(PM_FNAME); + char type; + + /* FNAME:$fname$ */ + bsnprintf(cmd.c_str(), cmd.size(), "FNAME:%s\n", rp->ofname); + backendctx->comm->write_command(ctx, cmd.c_str()); + DMSG(ctx, DINFO, "createFile:%s", cmd.c_str()); + /* STAT:... */ + switch (rp->type){ + case FT_REG: + type = 'F'; + break; + case FT_REGE: + type = 'E'; + break; + case FT_DIREND: + type = 'D'; + break; + case FT_LNK: + type = 'S'; + break; + default: + type = 'F'; + } + bsnprintf(cmd.c_str(), cmd.size(), "STAT:%c %lld %d %d %06o %d %d\n", type, rp->statp.st_size, + rp->statp.st_uid, rp->statp.st_gid, rp->statp.st_mode, (int)rp->statp.st_nlink, rp->LinkFI); + backendctx->comm->write_command(ctx, cmd.c_str()); + DMSG(ctx, DINFO, "createFile:%s", cmd.c_str()); + /* TSTAMP:... */ + if (rp->statp.st_atime || rp->statp.st_mtime || rp->statp.st_ctime){ + bsnprintf(cmd.c_str(), cmd.size(), "TSTAMP:%ld %ld %ld\n", rp->statp.st_atime, rp->statp.st_mtime, rp->statp.st_ctime); + backendctx->comm->write_command(ctx, cmd.c_str()); + DMSG(ctx, DINFO, "createFile:%s", cmd.c_str()); + } + /* LSTAT:$link$ */ + if (type == 'S' && rp->olname != NULL){ + bsnprintf(cmd.c_str(), cmd.size(), "LSTAT:%s\n", rp->olname); + backendctx->comm->write_command(ctx, cmd.c_str()); + DMSG(ctx, DINFO, "createFile:%s", cmd.c_str()); + } + backendctx->comm->signal_eod(ctx); + + /* check if backend accepted the file */ + if (backendctx->comm->read_command(ctx, cmd) > 0){ + DMSG(ctx, DINFO, "createFile:resp: %s\n", cmd.c_str()); + if (strcmp(cmd.c_str(), "OK") == 0){ + rp->create_status = CF_EXTRACT; + } else + if (strcmp(cmd.c_str(), "SKIP") == 0){ + rp->create_status = CF_SKIP; + } else { + DMSG(ctx, DERROR, "Wrong backend response to create file, got: %s\n", cmd.c_str()); + JMSG(ctx, backendctx->comm->is_fatal() ? M_FATAL : M_ERROR, "Wrong backend response to create file, got: %s\n", cmd.c_str()); + rp->create_status = CF_ERROR; + return bRC_Error; + } + } else { + if (backendctx->comm->is_error()){ + /* raise up error from backend */ + rp->create_status = CF_ERROR; + return bRC_Error; + } + } + + return bRC_OK; +} + +/* + * Unimplemented, always return bRC_OK. + */ +bRC METAPLUGIN::setFileAttributes(bpContext *ctx, struct restore_pkt *rp) +{ + return bRC_OK; +} + +/** + * @brief + * + * @param ctx + * @param exepath + * @return bRC + */ +bRC METAPLUGIN::setup_backend_command(bpContext *ctx, const char *exepath) +{ + if (exepath != NULL) + { + DMSG(ctx, DINFO, "ExePath: %s\n", exepath); + Mmsg(backend_cmd, "%s/%s", exepath, BACKEND_CMD); + if (access(backend_cmd.c_str(), X_OK) < 0) + { + berrno be; + DMSG2(ctx, DERROR, "Unable to use backend: %s Err=%s\n", backend_cmd.c_str(), be.bstrerror()); + JMSG2(ctx, M_FATAL, "Unable to use backend: %s Err=%s\n", backend_cmd.c_str(), be.bstrerror()); + return bRC_Error; + } + + return bRC_OK; + } + + return bRC_Error; +} + +/** + * @brief + * + * @param ctx + * @param xacl + * @return bRC + */ +bRC METAPLUGIN::handleXACLdata(bpContext *ctx, struct xacl_pkt *xacl) +{ + switch (xacl->func){ + case BACL_BACKUP: + if (readacl){ + DMSG0(ctx, DINFO, "bacl_backup\n"); + xacl->count = acldatalen; + xacl->content = acldata.c_str(); + readacl= false; + } else { + xacl->count = 0; + } + break; + case BACL_RESTORE: + DMSG0(ctx, DINFO, "bacl_restore\n"); + return perform_write_acl(ctx, xacl); + case BXATTR_BACKUP: + if (readxattr){ + DMSG0(ctx, DINFO, "bxattr_backup\n"); + xacl->count = xattrdatalen; + xacl->content = xattrdata.c_str(); + readxattr= false; + } else { + xacl->count = 0; + } + break; + case BXATTR_RESTORE: + DMSG0(ctx, DINFO, "bxattr_restore\n"); + return perform_write_xattr(ctx, xacl); + } + return bRC_OK; +} + +/* + * QueryParameter interface + */ +bRC METAPLUGIN::queryParameter(bpContext *ctx, struct query_pkt *qp) +{ + bRC ret = bRC_More; + OutputWriter ow(qp->api_opts); + POOL_MEM cmd(PM_MESSAGE); + char *p, *q, *t; + alist values(10, not_owned_by_alist); + key_pair *kp; + + DMSG0(ctx, D1, "METAPLUGIN::queryParameter\n"); + + if (listing == ListingNone){ + listing = QueryParams; + Mmsg(cmd, "%s query=%s", qp->command, qp->parameter); + if (prepare_backend(ctx, BACKEND_JOB_INFO_ESTIMATE, cmd.c_str()) == bRC_Error){ + return bRC_Error; + } + } + + /* read backend response */ + if (backendctx->comm->read_command(ctx, cmd) < 0){ + DMSG(ctx, DERROR, "Cannot read backend query response for %s command.\n", qp->parameter); + JMSG(ctx, backendctx->comm->is_fatal() ? M_FATAL : M_ERROR, "Cannot read backend query response for %s command.\n", qp->parameter); + return bRC_Error; + } + + /* check EOD */ + if (backendctx->comm->is_eod()){ + /* got EOD so the backend finish response, so terminate the chat */ + DMSG0(ctx, D1, "METAPLUGIN::queryParameter: got EOD\n"); + backendctx->comm->signal_term(ctx); + backendctx->comm->terminate(ctx); + ret = bRC_OK; + } else { + /* + * here we have: + * key=value[,key2=value2[,...]] + * parameters we should decompose + */ + p = cmd.c_str(); + while (*p != '\0'){ + q = strchr(p, ','); + if (q != NULL){ + *q++ = '\0'; + } + // single key=value + DMSG(ctx, D1, "METAPLUGIN::queryParameter:scan %s\n", p); + if ((t = strchr(p, '=')) != NULL){ + *t++ = '\0'; + } else { + t = (char*)""; // pointer to empty string + } + DMSG2(ctx, D1, "METAPLUGIN::queryParameter:pair '%s' = '%s'\n", p, t); + if (strlen(p) > 0){ + // push values only when we have key name + kp = New(key_pair(p, t)); + values.append(kp); + } + p = q != NULL ? q : (char*)""; + } + + // if more values then one then it is a list + if (values.size() > 1){ + DMSG0(ctx, D1, "METAPLUGIN::queryParameter: will render list\n") + ow.start_list(qp->parameter); + } + // render all values + foreach_alist(kp, &values){ + ow.get_output(OT_STRING, kp->key.c_str(), kp->value.c_str(), OT_END); + delete kp; + } + if (values.size() > 1){ + ow.end_list(); + } + + /* allocate working buffer, we use robjbuf variable for that as it will be freed at dtor */ + if (!robjbuf){ + robjbuf = get_pool_memory(PM_MESSAGE); + } + + pm_strcpy(&robjbuf, ow.get_output(OT_END)); + qp->result = robjbuf; + } + + return ret; +} + +/* + * Called here to make a new instance of the plugin -- i.e. when + * a new Job is started. There can be multiple instances of + * each plugin that are running at the same time. Your + * plugin instance must be thread safe and keep its own + * local data. + */ +static bRC newPlugin(bpContext *ctx) +{ + int JobId; + char *exepath; + METAPLUGIN *self = New(METAPLUGIN(ctx)); + POOL_MEM exepath_clean(PM_FNAME); + + if (!self) + return bRC_Error; + + ctx->pContext = (void*) self; + + /* setup the backend command */ + getBaculaVar(bVarExePath, (void *)&exepath); + DMSG(ctx, DINFO, "bVarExePath: %s\n", exepath); + pm_strcpy(exepath_clean, exepath); + strip_trailing_slashes(exepath_clean.c_str()); + + if (self->setup_backend_command(ctx, exepath_clean.c_str()) != bRC_OK) + { + DMSG0(ctx, DERROR, "Cannot setup backend!\n"); + JMSG0(ctx, M_FATAL, "Cannot setup backend!\n"); + return bRC_Error; + } + + getBaculaVar(bVarJobId, (void *)&JobId); + DMSG(ctx, D1, "newPlugin JobId=%d\n", JobId); + return bRC_OK; +} + +/* + * Release everything concerning a particular instance of + * a plugin. Normally called when the Job terminates. + */ +static bRC freePlugin(bpContext *ctx) +{ + if (!ctx){ + return bRC_Error; + } + METAPLUGIN *self = pluginclass(ctx); + DMSG(ctx, D1, "freePlugin this=%p\n", self); + if (!self){ + return bRC_Error; + } + delete self; + return bRC_OK; +} + +/* + * Called by core code to get a variable from the plugin. + * Not currently used. + */ +static bRC getPluginValue(bpContext *ctx, pVariable var, void *value) +{ + ASSERT_CTX; + + DMSG0(ctx, D3, "getPluginValue called.\n"); + METAPLUGIN *self = pluginclass(ctx); + return self->getPluginValue(ctx,var, value); +} + +/* + * Called by core code to set a plugin variable. + * Not currently used. + */ +static bRC setPluginValue(bpContext *ctx, pVariable var, void *value) +{ + ASSERT_CTX; + + DMSG0(ctx, D3, "setPluginValue called.\n"); + METAPLUGIN *self = pluginclass(ctx); + return self->setPluginValue(ctx, var, value); +} + +/* + * Called by Bacula when there are certain events that the + * plugin might want to know. The value depends on the + * event. + */ +static bRC handlePluginEvent(bpContext *ctx, bEvent *event, void *value) +{ + ASSERT_CTX; + + DMSG(ctx, D1, "handlePluginEvent (%i)\n", event->eventType); + METAPLUGIN *self = pluginclass(ctx); + return self->handlePluginEvent(ctx, event, value); +} + +/* + * Called when starting to backup a file. Here the plugin must + * return the "stat" packet for the directory/file and provide + * certain information so that Bacula knows what the file is. + * The plugin can create "Virtual" files by giving them + * a name that is not normally found on the file system. + */ +static bRC startBackupFile(bpContext *ctx, struct save_pkt *sp) +{ + ASSERT_CTX; + if (!sp) + return bRC_Error; + + DMSG0(ctx, D1, "startBackupFile.\n"); + METAPLUGIN *self = pluginclass(ctx); + return self->startBackupFile(ctx, sp); +} + +/* + * Done backing up a file. + */ +static bRC endBackupFile(bpContext *ctx) +{ + ASSERT_CTX; + + DMSG0(ctx, D1, "endBackupFile.\n"); + METAPLUGIN *self = pluginclass(ctx); + return self->endBackupFile(ctx); +} + +/* + * Called when starting restore the file, right after a createFile(). + */ +static bRC startRestoreFile(bpContext *ctx, const char *cmd) +{ + ASSERT_CTX; + + DMSG0(ctx, D1, "startRestoreFile.\n"); + METAPLUGIN *self = pluginclass(ctx); + return self->startRestoreFile(ctx, cmd); +} + +/* + * Done restore the file. + */ +static bRC endRestoreFile(bpContext *ctx) +{ + ASSERT_CTX; + + DMSG0(ctx, D1, "endRestoreFile.\n"); + METAPLUGIN *self = pluginclass(ctx); + return self->endRestoreFile(ctx); +} + +/* + * Do actual I/O. Bacula calls this after startBackupFile + * or after startRestoreFile to do the actual file + * input or output. + */ +static bRC pluginIO(bpContext *ctx, struct io_pkt *io) +{ + ASSERT_CTX; + + DMSG0(ctx, DVDEBUG, "pluginIO.\n"); + METAPLUGIN *self = pluginclass(ctx); + return self->pluginIO(ctx, io); +} + +/* + * Called here to give the plugin the information needed to + * re-create the file on a restore. It basically gets the + * stat packet that was created during the backup phase. + * This data is what is needed to create the file, but does + * not contain actual file data. + */ +static bRC createFile(bpContext *ctx, struct restore_pkt *rp) +{ + ASSERT_CTX; + + DMSG0(ctx, D1, "createFile.\n"); + METAPLUGIN *self = pluginclass(ctx); + return self->createFile(ctx, rp); +} + +/* + * Called after the file has been restored. This can be used to + * set directory permissions, ... + */ +static bRC setFileAttributes(bpContext *ctx, struct restore_pkt *rp) +{ + ASSERT_CTX; + + DMSG0(ctx, D1, "setFileAttributes.\n"); + METAPLUGIN *self = pluginclass(ctx); + return self->setFileAttributes(ctx, rp); +} + +/* + * handleXACLdata used for ACL/XATTR backup and restore + */ +static bRC handleXACLdata(bpContext *ctx, struct xacl_pkt *xacl) +{ + ASSERT_CTX; + + DMSG(ctx, D1, "handleXACLdata: %i\n", xacl->func); + METAPLUGIN *self = pluginclass(ctx); + return self->handleXACLdata(ctx, xacl); +} + +/* + * Used for debug backend context and contexts list. Unused for production. + * in: + * bpContext - bacula plugin context + * out: + * backendlist and backendctx data logged in debug trace file + */ +void METAPLUGIN::dump_backendlist(bpContext *ctx) +{ + BackendCTX *pini; + + if (backendlist != NULL){ + DMSG(ctx, DINFO, "## backendlist allocated: %p\n", backendlist); + DMSG(ctx, DINFO, "## backendlist size: %d\n", backendlist->size()); + foreach_alist(pini, backendlist){ + DMSG2(ctx, DINFO, "## backendlist ctx: %p cmd: %s\n", pini, pini->cmd); + } + } else { + DMSG0(ctx, DINFO, "## backendlist null\n"); + } + if (backendctx != NULL){ + DMSG(ctx, DINFO, "## backendctx: %p\n", backendctx); + } else { + DMSG0(ctx, DINFO, "## backendctx null\n"); + } +} + +/* QueryParameter interface */ +static bRC queryParameter(bpContext *ctx, struct query_pkt *qp) +{ + ASSERT_CTX; + + DMSG2(ctx, D1, "queryParameter: %s:%s\n", qp->command, qp->parameter); + METAPLUGIN *self = pluginclass(ctx); + return self->queryParameter(ctx, qp); +} diff --git a/bacula/src/plugins/fd/pluginlib/metaplugin.h b/bacula/src/plugins/fd/pluginlib/metaplugin.h new file mode 100644 index 000000000..44b3808df --- /dev/null +++ b/bacula/src/plugins/fd/pluginlib/metaplugin.h @@ -0,0 +1,203 @@ +/* + Bacula® - The Network Backup Solution + + Copyright (C) 2007-2017 Bacula Systems SA + All rights reserved. + + The main author of Bacula is Kern Sibbald, with contributions from many + others, a complete list can be found in the file AUTHORS. + + Licensees holding a valid Bacula Systems SA license may use this file + and others of this release in accordance with the proprietary license + agreement provided in the LICENSE file. Redistribution of any part of + this release is not permitted. + + Bacula® is a registered trademark of Kern Sibbald. +*/ +/** + * @file metaplugin.h + * @author Radosław Korzeniewski (radoslaw@korzeniewski.net) + * @brief This is a Bacula metaplugin interface. + * @version 2.1.0 + * @date 2020-12-23 + * + * @copyright Copyright (c) 2020 All rights reserved. IP transferred to Bacula Systems according to agreement. + */ + +#include "pluginlib.h" +#include "ptcomm.h" +#include "lib/ini.h" + + +#define USE_CMD_PARSER +#include "fd_common.h" + +#ifndef _METAPLUGIN_H_ +#define _METAPLUGIN_H_ + + +// Plugin Info definitions +extern const char *PLUGIN_LICENSE; +extern const char *PLUGIN_AUTHOR; +extern const char *PLUGIN_DATE; +extern const char *PLUGIN_VERSION; +extern const char *PLUGIN_DESCRIPTION; + +// Plugin linking time variables +extern const char *PLUGINPREFIX; +extern const char *PLUGINNAME; +extern const char *PLUGINAPI; +extern const char *BACKEND_CMD; + +// The list of restore options saved to the RestoreObject. +extern struct ini_items plugin_items_dump[]; + +// the list of valid plugin options +extern const char *valid_params[]; + +// types used by Plugin + + +/* + * This is a single backend context for backup and restore. + * The BackendCTX is used especially during multiple Plugin=... parameters + * defined in FileSet. Each Plugin=... parameter will have a dedicated + * backend handling job data. All of these backends will be executed + * concurrently, especially for restore where restored streams could achieve + * in different order. The allocated contests will be stored in a simple list. + */ +class BackendCTX : public SMARTALLOC +{ +public: + char *cmd; + PTCOMM *comm; + cmd_parser *parser; + ConfigFile *ini; + + BackendCTX(char *command); + ~BackendCTX(); +}; + +/* + * This is a main plugin API class. It manages a plugin context. + * All the public methods correspond to a public Bacula API calls, even if + * a callback is not implemented. + */ +class METAPLUGIN: public SMARTALLOC +{ +public: + enum class MODE + { + NONE = 0, + BACKUP_FULL, + BACKUP_INCR, + BACKUP_DIFF, + Estimate, + Listing, + QueryParams, + RESTORE, + }; + + POOL_MEM backend_cmd; + + bRC getPluginValue(bpContext *ctx, pVariable var, void *value); + bRC setPluginValue(bpContext *ctx, pVariable var, void *value); + bRC handlePluginEvent(bpContext *ctx, bEvent *event, void *value); + bRC startBackupFile(bpContext *ctx, struct save_pkt *sp); + bRC endBackupFile(bpContext *ctx); + bRC startRestoreFile(bpContext *ctx, const char *cmd); + bRC endRestoreFile(bpContext *ctx); + bRC pluginIO(bpContext *ctx, struct io_pkt *io); + bRC createFile(bpContext *ctx, struct restore_pkt *rp); + bRC setFileAttributes(bpContext *ctx, struct restore_pkt *rp); + bRC handleXACLdata(bpContext *ctx, struct xacl_pkt *xacl); + bRC queryParameter(bpContext *ctx, struct query_pkt *qp); + bRC setup_backend_command(bpContext *ctx, const char *exepath); + METAPLUGIN(bpContext *bpctx); +#if __cplusplus > 201103L + METAPLUGIN() = delete; + METAPLUGIN(METAPLUGIN&) = delete; + METAPLUGIN(METAPLUGIN&&) = delete; +#endif + ~METAPLUGIN(); + +private: + enum ListingMode + { + ListingNone, + Listing, + QueryParams, + }; + + bpContext *ctx; // Bacula Plugin Context + MODE mode; // Plugin mode of operation + int JobId; // Job ID + char *JobName; // Job name + time_t since; // Job since parameter + char *where; // the Where variable for restore job if set by user + char *regexwhere; // the RegexWhere variable for restore job if set by user + char replace; // the replace variable for restore job + bool robjsent; // set when RestoreObject was sent during Full backup + bool estimate; // used when mode is METAPLUGIN_BACKUP_* but we are doing estimate only + ListingMode listing; // used for a Listing procedure for estimate + bool nodata; // set when backend signaled no data for backup or no data for restore + bool nextfile; // set when IO_CLOSE got FNAME: command + bool openerror; // show if "openfile" was unsuccessful + bool pluginobject; // set when IO_CLOSE got FNAME: command + bool readacl; // got ACL data from backend + bool readxattr; // got XATTR data from backend + bool accurate_warning; // for sending accurate mode warning once */ + // TODO: define a variable which will signal job cancel + BackendCTX *backendctx; // the current backend context + alist *backendlist; // the backend context list for multiple backend execution for a single job + POOL_MEM fname; // current file name to backup (grabbed from backend) + POOLMEM *lname; // current LSTAT data if any + POOLMEM *robjbuf; // the buffer for restore object data + POOL_MEM plugin_obj_cat; // + POOL_MEM plugin_obj_type; // + POOL_MEM plugin_obj_name; // + POOL_MEM plugin_obj_src; // + POOL_MEM plugin_obj_uuid; // + uint64_t plugin_obj_size; // + int acldatalen; // the length of the data in acl buffer + POOL_MEM acldata; // the buffer for ACL data received from backend + int xattrdatalen; // the length of the data in xattr buffer + POOL_MEM xattrdata; // the buffer for XATTR data received from backend + + bRC parse_plugin_command(bpContext *ctx, const char *command, alist *params); + bRC parse_plugin_restoreobj(bpContext *ctx, restore_object_pkt *rop); + bRC run_backend(bpContext *ctx); + bRC send_parameters(bpContext *ctx, char *command); + bRC send_jobinfo(bpContext *ctx, char type); + bRC send_startjob(bpContext *ctx, const char *command); + bRC send_startbackup(bpContext *ctx); + bRC send_startestimate(bpContext *ctx); + bRC send_startlisting(bpContext *ctx); + bRC send_startrestore(bpContext *ctx); + bRC send_endjob(bpContext *ctx); + bRC prepare_backend(bpContext *ctx, char type, char *command); + bRC perform_backup_open(bpContext *ctx, struct io_pkt *io); + bRC perform_restore_open(bpContext *ctx, struct io_pkt *io); + bRC perform_read_data(bpContext *ctx, struct io_pkt *io); + bRC perform_write_data(bpContext *ctx, struct io_pkt *io); + bRC perform_write_end(bpContext *ctx, struct io_pkt *io); + bRC perform_read_metadata(bpContext *ctx); + bRC perform_read_fstatdata(bpContext *ctx, struct save_pkt *sp); + bRC perform_read_pluginobject(bpContext *ctx, struct save_pkt *sp); + bRC perform_read_acl(bpContext *ctx); + bRC perform_write_acl(bpContext *ctx, struct xacl_pkt * xacl); + bRC perform_read_xattr(bpContext *ctx); + bRC perform_write_xattr(bpContext *ctx, struct xacl_pkt * xacl); + int check_ini_param(ConfigFile *ini, char *param); + bool check_plugin_param(const char *param, alist *params); + int get_ini_count(ConfigFile *ini); + void new_backendlist(bpContext *ctx, char *command); + bRC switch_or_run_backend(bpContext *ctx, char *command); + bRC terminate_current_backend(bpContext *ctx); + bRC terminate_all_backends(bpContext *ctx); + bRC signal_finish_all_backends(bpContext *ctx); + bRC render_param(bpContext *ctx, POOLMEM *param, INI_ITEM_HANDLER *handler, char *key, item_value val); + void dump_backendlist(bpContext *ctx); +}; + +#endif /* _METAPLUGIN_H_ */ diff --git a/bacula/src/plugins/fd/pluginlib/pluginlib.h b/bacula/src/plugins/fd/pluginlib/pluginlib.h index 84ca5897b..06a565053 100644 --- a/bacula/src/plugins/fd/pluginlib/pluginlib.h +++ b/bacula/src/plugins/fd/pluginlib/pluginlib.h @@ -37,6 +37,10 @@ extern bFuncs *bfuncs; extern bInfo *binfo; +// Plugin linking time variables +extern const char *PLUGINPREFIX; +extern const char *PLUGINNAME; + /* module definition */ #ifndef PLUGMODULE #define PLUGMODULE "PluginLib::" @@ -48,30 +52,30 @@ extern bInfo *binfo; /* debug and messages functions */ #define JMSG0(ctx,type,msg) \ - if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, PLUGINPREFIX " " msg ); + if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, "%s " msg, PLUGINPREFIX ); #define JMSG1 JMSG #define JMSG(ctx,type,msg,var) \ - if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, PLUGINPREFIX " " msg, var ); + if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, "%s " msg, PLUGINPREFIX, var ); #define JMSG2(ctx,type,msg,var1,var2) \ - if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, PLUGINPREFIX " " msg, var1, var2 ); + if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, "%s " msg, PLUGINPREFIX, var1, var2 ); #define JMSG3(ctx,type,msg,var1,var2,var3) \ - if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, PLUGINPREFIX " " msg, var1, var2, var3 ); + if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, "%s " msg, PLUGINPREFIX, var1, var2, var3 ); #define JMSG4(ctx,type,msg,var1,var2,var3,var4) \ - if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, PLUGINPREFIX " " msg, var1, var2, var3, var4 ); + if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, "%s " msg, PLUGINPREFIX, var1, var2, var3, var4 ); #define DMSG0(ctx,level,msg) \ - if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, PLUGINPREFIX " " msg ); + if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, "%s " msg, PLUGINPREFIX ); #define DMSG1 DMSG #define DMSG(ctx,level,msg,var) \ - if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, PLUGINPREFIX " " msg, var ); + if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, "%s " msg, PLUGINPREFIX, var ); #define DMSG2(ctx,level,msg,var1,var2) \ - if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, PLUGINPREFIX " " msg, var1, var2 ); + if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, "%s " msg, PLUGINPREFIX, var1, var2 ); #define DMSG3(ctx,level,msg,var1,var2,var3) \ - if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, PLUGINPREFIX " " msg, var1, var2, var3 ); + if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, "%s " msg, PLUGINPREFIX, var1, var2, var3 ); #define DMSG4(ctx,level,msg,var1,var2,var3,var4) \ - if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, PLUGINPREFIX " " msg, var1, var2, var3, var4 ); + if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, "%s " msg, PLUGINPREFIX, var1, var2, var3, var4 ); #define DMSG6(ctx,level,msg,var1,var2,var3,var4,var5,var6) \ - if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, PLUGINPREFIX " " msg, var1, var2, var3, var4, var5, var6 ); + if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, "%s " msg, PLUGINPREFIX, var1, var2, var3, var4, var5, var6 ); /* fixed debug level definitions */ #define D1 1 /* debug for every error */ @@ -99,11 +103,11 @@ extern bInfo *binfo; #define DMSG_EVENT_PTR(event,value) DMSG2(ctx, DINFO, "%s value=%p\n", eventtype2str(event), value); /* pure debug macros */ -#define DMsg0(level,msg) Dmsg1(level,PLUGMODULE "%s: " msg,__func__) -#define DMsg1(level,msg,a1) Dmsg2(level,PLUGMODULE "%s: " msg,__func__,a1) -#define DMsg2(level,msg,a1,a2) Dmsg3(level,PLUGMODULE "%s: " msg,__func__,a1,a2) -#define DMsg3(level,msg,a1,a2,a3) Dmsg4(level,PLUGMODULE "%s: " msg,__func__,a1,a2,a3) -#define DMsg4(level,msg,a1,a2,a3,a4) Dmsg5(level,PLUGMODULE "%s: " msg,__func__,a1,a2,a3,a4) +#define DMsg0(level,msg) Dmsg1(level, PLUGMODULE "%s: " msg, __func__) +#define DMsg1(level,msg,a1) Dmsg2(level, PLUGMODULE "%s: " msg, __func__, a1) +#define DMsg2(level,msg,a1,a2) Dmsg3(level, PLUGMODULE "%s: " msg, __func__, a1, a2) +#define DMsg3(level,msg,a1,a2,a3) Dmsg4(level, PLUGMODULE "%s: " msg, __func__, a1, a2, a3) +#define DMsg4(level,msg,a1,a2,a3,a4) Dmsg5(level, PLUGMODULE "%s: " msg, __func__, a1, a2, a3, a4) #define BOOLSTR(b) (b?"True":"False") diff --git a/bacula/src/plugins/fd/pluginlib/ptcomm.cpp b/bacula/src/plugins/fd/pluginlib/ptcomm.cpp index fb9fb59f4..46fb6352a 100644 --- a/bacula/src/plugins/fd/pluginlib/ptcomm.cpp +++ b/bacula/src/plugins/fd/pluginlib/ptcomm.cpp @@ -28,8 +28,6 @@ #include #include -/* Plugin compile time variables required by pluglib */ -#define PLUGINPREFIX "ptcomm:" /* * libbac uses its own sscanf implementation which is not compatible with @@ -41,10 +39,6 @@ #endif // #define NEED_REVIEW -/* from lib/scan.c */ -extern int parse_args(POOLMEM *cmd, POOLMEM **args, int *argc, - char **argk, char **argv, int max_args); - /* * Closes external pipe if available (opened). *