]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
pluginlib: Refactoring PLUGINPREFIX to C++.
authorRadosław Korzeniewski <radekk@inteos.pl>
Wed, 23 Dec 2020 16:45:31 +0000 (17:45 +0100)
committerEric Bollengier <eric@baculasystems.com>
Thu, 24 Mar 2022 08:03:00 +0000 (09:03 +0100)
bacula/src/plugins/fd/pluginlib/Makefile.in
bacula/src/plugins/fd/pluginlib/metaplugin.cpp [new file with mode: 0644]
bacula/src/plugins/fd/pluginlib/metaplugin.h [new file with mode: 0644]
bacula/src/plugins/fd/pluginlib/pluginlib.h
bacula/src/plugins/fd/pluginlib/ptcomm.cpp

index 914bb58dba874c912df885f7af8b26809a8018e9..de9a16f5e744ac4a8505b14579441a5d7f1f1756 100644 (file)
@@ -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 (file)
index 0000000..c0a5118
--- /dev/null
@@ -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 <sys/stat.h>
+#include <signal.h>
+#include <sys/select.h>
+
+/*
+ * 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
+ *    <n> - whan a parameter param is found and <n> 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:
+ *    <n> - 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(&param, "%s=%s\n", key, val.strval);
+   } else
+   if (handler == ini_store_int64){
+      Mmsg(&param, "%s=%lld\n", key, val.int64val);
+   } else
+   if (handler == ini_store_bool){
+      Mmsg(&param, "%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(&param, "%s=%s\n", backendctx->parser->argk[i], backendctx->parser->argv[i]);
+            params->append(param);
+         } else {
+            Mmsg(&param, "%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, &params);
+   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, &params){
+      // 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, &params){
+      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
+ *    <other> - 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, &regexwhere);
+      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 = <plugin-name>: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 = <plugin-name>: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 = <plugin-name>: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 = <plugin-name>: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 (file)
index 0000000..44b3808
--- /dev/null
@@ -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_ */
index 84ca5897b53a0c59da9b3f17634021f3a7721849..06a5650537c4a0488c2990c778bbdd7211432f0d 100644 (file)
 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")
 
index fb9fb59f4e786d990677fbf94b963b1d716b76d1..46fb6352ac0f7f679892f6d905331905ec6bb3ab 100644 (file)
@@ -28,8 +28,6 @@
 #include <sys/stat.h>
 #include <signal.h>
 
-/* Plugin compile time variables required by pluglib */
-#define PLUGINPREFIX            "ptcomm:"
 
 /*
  * libbac uses its own sscanf implementation which is not compatible with
 #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).
  *