--- /dev/null
+#
+# Makefile for building FD plugins PluginLibrary for Bacula
+#
+# Copyright (C) 2000-2020 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+
+# Author: Radoslaw Korzeniewski, radekk@inteos.pl, Inteos Sp. z o.o.
+
+@MCOMMON@
+
+# No optimization for now for easy debugging
+
+SRCDIR = ../../..
+FDDIR = $(SRCDIR)/filed
+LIBDIR = $(SRCDIR)/lib
+FINDLIBDIR = $(SRCDIR)/findlib
+
+topdir = @BUILD_DIR@
+thisdir = src/plugins/fd/pluginlib
+
+UNITTESTSOBJ = $(LIBDIR)/unittests.lo
+
+PLUGINLIBSSRC = pluginlib.cpp pluginlib.h
+PLUGINLIBSOBJ = $(filter %.lo,$(PLUGINLIBSSRC:.cpp=.lo))
+ISO8601SRC = iso8601.cpp iso8601.h
+ISO8601OBJ = $(filter %.lo,$(ISO8601SRC:.cpp=.lo))
+EXECPROGSRC = execprog.cpp execprog.h
+EXECPROGOBJ = $(filter %.lo,$(EXECPROGSRC:.cpp=.lo))
+COMMCTXSRC = commctx.cpp commctx.h
+COMMCTXOBJ = $(filter %.lo,$(COMMCTXSRC:.cpp=.lo))
+
+PLUGINLIBSTEST = pluginlib_test.cpp $(PLUGINLIBSSRC) $(UNITTESTSOBJ)
+PLUGINLIBSTESTOBJ = $(filter %.lo,$(PLUGINLIBSTEST:.cpp=.lo))
+ISO8601TEST = iso8601_test.cpp $(ISO8601SRC) $(UNITTESTSOBJ)
+ISO8601TESTOBJ = $(filter %.lo,$(ISO8601TEST:.cpp=.lo))
+
+COMMONPLUGINOBJ = $(PLUGINLIBSOBJ) $(ISO8601OBJ) $(EXECPROGOBJ)
+COMMONPLUGINTESTS = pluginlib_test iso8601_test
+
+.SUFFIXES: .c .lo
+
+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 $<
+
+.cpp.lo:
+ @echo "Compiling c++ $< ..."
+ $(NO_ECHO)$(LIBTOOL_COMPILE) $(CXX) $(DEFS) $(DEBUG) $(CPPFLAGS) $(CFLAGS) -I$(SRCDIR) -I$(FDDIR) -I$(LIBDIR) -I$(FINDLIBDIR) -I. -c $<
+
+all: $(COMMONPLUGINOBJ) $(COMMONPLUGINTESTS)
+
+$(LIBDIR)/unittests.lo:
+ $(MAKE) -C $(LIBDIR) unittests.lo
+
+pluginlib_test: Makefile $(PLUGINLIBSTESTOBJ) $(PLUGINLIBSSRC)
+ @echo "Building $@ ..."
+ $(NO_ECHO)$(LIBTOOL_LINK) --silent $(CXX) $(LDFLAGS) $(LIBCURL) $(LIBBAC) $(PLUGINLIBSTESTOBJ) -o $@
+
+iso8601_test: Makefile $(ISO8601TESTOBJ) $(ISO8601SRC)
+ @echo "Building $@ ..."
+ $(NO_ECHO)$(LIBTOOL_LINK) --silent $(CXX) $(LDFLAGS) $(LIBCURL) $(LIBBAC) $(PLUGINLIBSTESTOBJ) -o $@
+
+install: all
+ $(MKDIR) $(DESTDIR)$(plugindir)
+ $(LIBTOOL_INSTALL) $(INSTALL_PROGRAM) bpipe-fd.la $(DESTDIR)$(plugindir)
+ $(RMF) $(DESTDIR)$(plugindir)/bpipe-fd.la
+
+install-test-plugin: all
+ $(MKDIR) $(DESTDIR)$(plugindir)
+ $(LIBTOOL_INSTALL) $(INSTALL_PROGRAM) test-plugin-fd.la $(DESTDIR)$(plugindir)
+ $(RMF) $(DESTDIR)$(plugindir)/test-plugin-fd.la
+ $(LIBTOOL_INSTALL) $(INSTALL_PROGRAM) test-deltaseq-fd.la $(DESTDIR)$(plugindir)
+ $(RMF) $(DESTDIR)$(plugindir)/test-deltaseq-fd.la
+ $(LIBTOOL_INSTALL) $(INSTALL_PROGRAM) test-handlexacl-plugin-fd.la $(DESTDIR)$(plugindir)
+ $(RMF) $(DESTDIR)$(plugindir)/test-handlexacl-plugin-fd.la
+
+Makefile: Makefile.in $(topdir)/config.status
+ cd $(topdir) \
+ && CONFIG_FILES=$(thisdir)/$@ CONFIG_HEADERS= $(SHELL) ./config.status
+
+libtool-clean:
+ @find . -name '*.lo' -print | xargs $(LIBTOOL_CLEAN) $(RMF)
+ @$(RMF) *.la
+ @$(RMF) -r .libs _libs
+
+clean: libtool-clean
+ @rm -f main *.so *.o 1 2 3
+ @rm -f $(COMMONPLUGINTESTS)
+
+distclean: clean
+ @rm -f Makefile *.la *.lo
+ @rm -rf .libs
+
+libtool-uninstall:
+ $(LIBTOOL_UNINSTALL) $(RMF) $(DESTDIR)$(plugindir)/bpipe-fd.so
+
+uninstall: @LIBTOOL_UNINSTALL_TARGET@
+
+depend:
--- /dev/null
+/*
+ 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.
+*/
+/*
+ *
+ * All rights reserved. IP transferred to Bacula Systems according to agreement.
+ *
+ * Common definitions and utility functions for Inteos plugins.
+ * Functions defines a common framework used in our utilities and plugins.
+ * Author: Radosław Korzeniewski, radekk@inteos.pl, Inteos Sp. z o.o.
+ */
+
+#include "pluginlib.h"
+
+
+/* Events that are passed to plugin
+typedef enum {
+ bEventJobStart = 1,
+ bEventJobEnd = 2,
+ bEventStartBackupJob = 3,
+ bEventEndBackupJob = 4,
+ bEventStartRestoreJob = 5,
+ bEventEndRestoreJob = 6,
+ bEventStartVerifyJob = 7,
+ bEventEndVerifyJob = 8,
+ bEventBackupCommand = 9,
+ bEventRestoreCommand = 10,
+ bEventEstimateCommand = 11,
+ bEventLevel = 12,
+ bEventSince = 13,
+ bEventCancelCommand = 14,
+ bEventVssBackupAddComponents = 15,
+ bEventVssRestoreLoadComponentMetadata = 16,
+ bEventVssRestoreSetComponentsSelected = 17,
+ bEventRestoreObject = 18,
+ bEventEndFileSet = 19,
+ bEventPluginCommand = 20,
+ bEventVssBeforeCloseRestore = 21,
+ bEventVssPrepareSnapshot = 22,
+ bEventOptionPlugin = 23,
+ bEventHandleBackupFile = 24,
+ bEventComponentInfo = 25
+} bEventType;
+*/
+
+const char *eventtype2str(bEvent *event){
+ switch (event->eventType){
+ case bEventJobStart:
+ return "bEventJobStart";
+ case bEventJobEnd:
+ return "bEventJobEnd";
+ case bEventStartBackupJob:
+ return "bEventStartBackupJob";
+ case bEventEndBackupJob:
+ return "bEventEndBackupJob";
+ case bEventStartRestoreJob:
+ return "bEventStartRestoreJob";
+ case bEventEndRestoreJob:
+ return "bEventEndRestoreJob";
+ case bEventStartVerifyJob:
+ return "bEventStartVerifyJob";
+ case bEventEndVerifyJob:
+ return "bEventEndVerifyJob";
+ case bEventBackupCommand:
+ return "bEventBackupCommand";
+ case bEventRestoreCommand:
+ return "bEventRestoreCommand";
+ case bEventEstimateCommand:
+ return "bEventEstimateCommand";
+ case bEventLevel:
+ return "bEventLevel";
+ case bEventSince:
+ return "bEventSince";
+ case bEventCancelCommand:
+ return "bEventCancelCommand";
+ case bEventVssBackupAddComponents:
+ return "bEventVssBackupAddComponents";
+ case bEventVssRestoreLoadComponentMetadata:
+ return "bEventVssRestoreLoadComponentMetadata";
+ case bEventVssRestoreSetComponentsSelected:
+ return "bEventVssRestoreSetComponentsSelected";
+ case bEventRestoreObject:
+ return "bEventRestoreObject";
+ case bEventEndFileSet:
+ return "bEventEndFileSet";
+ case bEventPluginCommand:
+ return "bEventPluginCommand";
+ case bEventVssBeforeCloseRestore:
+ return "bEventVssBeforeCloseRestore";
+ case bEventVssPrepareSnapshot:
+ return "bEventVssPrepareSnapshot";
+ case bEventOptionPlugin:
+ return "bEventOptionPlugin";
+ case bEventHandleBackupFile:
+ return "bEventHandleBackupFile";
+ case bEventComponentInfo:
+ return "bEventComponentInfo";
+ default:
+ return "Unknown";
+ }
+}
+
+
+/*
+ * Return the real size of the disk based on the size suffix.
+ *
+ * in:
+ * disksize - the numeric value of the disk size to compute
+ * suff - the suffix for a disksize value
+ * out:
+ * uint64_t - the size of the disk computed with suffix
+ */
+uint64_t pluglib_size_suffix(int disksize, char suff)
+{
+ uint64_t size;
+
+ switch (suff){
+ case 'G':
+ size = (uint64_t)disksize * 1024 * 1048576;
+ break;
+ case 'M':
+ size = (uint64_t)disksize * 1048576;
+ break;
+ case 'T':
+ size = (uint64_t)disksize * 1048576 * 1048576;
+ break;
+ case 'K':
+ case 'k':
+ size = (uint64_t)disksize * 1024;
+ break;
+ default:
+ size = disksize;
+ }
+ return size;
+}
+
+/*
+ * Return the real size of the disk based on the size suffix.
+ * This version uses a floating point numbers (double) for computation.
+ *
+ * in:
+ * disksize - the numeric value of the disk size to compute
+ * suff - the suffix for a disksize value
+ * out:
+ * uint64_t - the size of the disk computed with suffix
+ */
+uint64_t pluglib_size_suffix(double disksize, char suff)
+{
+ uint64_t size;
+
+ switch (suff){
+ case 'G':
+ size = disksize * 1024.0 * 1048576.0;
+ break;
+ case 'M':
+ size = disksize * 1048576.0;
+ break;
+ case 'T':
+ size = disksize * 1048576.0 * 1048576.0;
+ break;
+ case 'K':
+ case 'k':
+ size = disksize * 1024.0;
+ break;
+ default:
+ size = disksize;
+ }
+ return size;
+}
+
+/*
+ * Creates a path hierarchy on local FS.
+ * It is used for local restore mode to create a required directory.
+ * The functionality is similar to 'mkdir -p'.
+ *
+ * TODO: make a support for relative path
+ * TODO: check if we can use findlib/makepath implementation instead
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * path - a full path to create, does not check if the path is relative,
+ * could fail in this case
+ * out:
+ * bRC_OK - path creation was successful
+ * bRC_Error - on any error
+ */
+bRC pluglib_mkpath(bpContext* ctx, char* path, bool isfatal)
+{
+#ifdef PLUGINPREFIX
+#define _OLDPREFIX PLUGINPREFIX
+#endif
+#define PLUGINPREFIX "pluglibmkpath:"
+ struct stat statp;
+ POOL_MEM dir(PM_FNAME);
+ char *p, *q;
+
+ if (!path){
+ return bRC_Error;
+ }
+ if (stat(path, &statp) == 0){
+ if (S_ISDIR(statp.st_mode)){
+ return bRC_OK;
+ } else {
+ DMSG(ctx, DERROR, "Path %s is not directory\n", path);
+ JMSG(ctx, isfatal ? M_FATAL : M_ERROR, "Path %s is not directory\n", path);
+ return bRC_Error;
+ }
+ }
+ DMSG(ctx, DDEBUG, "mkpath verify dir: %s\n", path);
+ pm_strcpy(dir, path);
+ p = dir.addr() + 1;
+ while (*p && (q = strchr(p, (int)PathSeparator)) != NULL){
+ *q = 0;
+ DMSG(ctx, DDEBUG, "mkpath scanning(1): %s\n", dir.c_str());
+ if (stat(dir.c_str(), &statp) == 0){
+ *q = PathSeparator;
+ p = q + 1;
+ continue;
+ }
+ DMSG0(ctx, DDEBUG, "mkpath will create dir(1).\n");
+ if (mkdir(dir.c_str(), 0750) < 0){
+ /* error */
+ berrno be;
+ DMSG2(ctx, DERROR, "Cannot create directory %s Err=%s\n", dir.c_str(), be.bstrerror());
+ JMSG2(ctx, isfatal ? M_FATAL : M_ERROR, "Cannot create directory %s Err=%s\n", dir.c_str(), be.bstrerror());
+ return bRC_Error;
+ }
+ *q = PathSeparator;
+ p = q + 1;
+ }
+ DMSG0(ctx, DDEBUG, "mkpath will create dir(2).\n");
+ if (mkdir(path, 0750) < 0){
+ /* error */
+ berrno be;
+ DMSG2(ctx, DERROR, "Cannot create directory %s Err=%s\n", path, be.bstrerror());
+ JMSG2(ctx, isfatal ? M_FATAL : M_ERROR, "Cannot create directory %s Err=%s\n", path, be.bstrerror());
+ return bRC_Error;
+ }
+ DMSG0(ctx, DDEBUG, "mkpath finish.\n");
+#ifdef _OLDPREFIX
+#define PLUGINPREFIX _OLDPREFIX
+#undef _OLDPREFIX
+#else
+#undef PLUGINPREFIX
+#endif
+ return bRC_OK;
+}
+
+/**
+ * @brief
+ *
+ * @param str
+ * @param sep
+ * @return alist*
+ */
+alist * plugutil_str_split_to_alist(const char * str, const char sep)
+{
+ POOL_MEM buf(PM_NAME);
+ const char * p;
+ const char * q;
+ const char * s;
+ alist * list;
+
+ if (str == NULL || strlen(str) == 0){
+ return NULL;
+ }
+
+ list = New(alist(5, true));
+ p = str;
+
+ do {
+ // search for separator char - sep
+ q = strchr(p, sep);
+ if (q == NULL){
+ // copy whole string from p to buf
+ pm_strcpy(buf, p);
+ } else {
+ // copy string from p up to q
+ pm_memcpy(buf, p, q - p + 1);
+ buf.c_str()[q - p] = '\0';
+ p = q + 1; // next element
+ }
+ // in buf we have splitted string part
+ s = bstrdup(buf.c_str());
+ list->append((void*)s);
+ } while (q != NULL);
+
+ return list;
+}
+
+/*
+ * Render a xe tool parameter for string value.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * param - a pointer to the param variable where we will render a parameter
+ * pname - a name of the parameter to compare
+ * fmt - a low-level parameter name
+ * name - a name of the parameter from parameter list
+ * value - a value to render
+ * out:
+ * True if parameter was rendered
+ * False if it was not the parameter required
+ */
+bool render_param(POOLMEM **param, const char *pname, const char *fmt, const char *name, char *value)
+{
+ if (bstrcasecmp(name, pname)){
+ if (!*param){
+ *param = get_pool_memory(PM_NAME);
+ Mmsg(*param, " -%s '%s' ", fmt, value);
+ DMsg1(DDEBUG, "render param:%s\n", *param);
+ }
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Render a xe tool parameter for integer value.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * param - a pointer to the param variable where we will render a parameter
+ * pname - a name of the parameter to compare
+ * fmt - a low-level parameter name
+ * name - a name of the parameter from parameter list
+ * value - a value to render
+ * out:
+ * True if parameter was rendered
+ * False if it was not the parameter required
+ */
+bool render_param(POOLMEM **param, const char *pname, const char *fmt, const char *name, int value)
+{
+ if (bstrcasecmp(name, pname)){
+ if (!*param){
+ *param = get_pool_memory(PM_NAME);
+ Mmsg(*param, " -%s %d ", value);
+ DMsg1(DDEBUG, "render param:%s\n", *param);
+ }
+ return true;
+ }
+ return false;
+}
+
+/**
+ * @brief
+ *
+ * @param param
+ * @param pname
+ * @param name
+ * @param value
+ * @return true
+ * @return false
+ */
+bool parse_param(POOL_MEM ¶m, const char *pname, const char *name, char *value)
+{
+ if (bstrcasecmp(name, pname)){
+ pm_strcpy(param, value);
+ DMsg1(DDEBUG, "render param:%s\n", param.c_str());
+ return true;
+ }
+ return false;
+};
+
+/*
+ * Setup XECOMMCTX parameter for boolean value.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * param - a pointer to the param variable where we will render a parameter
+ * pname - a name of the parameter to compare
+ * name - a name of the parameter from parameter list
+ * value - a value to render
+ * out:
+ * True if parameter was rendered
+ * False if it was not the parameter required
+ */
+bool render_param(bool ¶m, const char *pname, const char *name, bool value)
+{
+ if (bstrcasecmp(name, pname)){
+ if (param){
+ param = value;
+ DMsg2(DDEBUG, "render param: %s=%s\n", pname, param ? "True" : "False");
+ }
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Setup XECOMMCTX parameter for boolean from string value.
+ * The parameter value will be false if value start with '0' character and
+ * will be true in any other case. So, when a plugin will have a following:
+ * param
+ * param=xxx
+ * param=1
+ * then a param will be set to true.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * param - a pointer to the param variable where we will render a parameter
+ * pname - a name of the parameter to compare
+ * name - a name of the parameter from parameter list
+ * value - a value to render
+ * out:
+ * True if parameter was rendered
+ * False if it was not the parameter required
+ */
+bool parse_param(bool ¶m, const char *pname, const char *name, char *value)
+{
+ if (bstrcasecmp(name, pname)){
+ if (value && *value == '0'){
+ param = false;
+ } else {
+ param = true;
+ }
+ DMsg2(DINFO, "%s parameter: %s\n", name, param ? "True" : "False");
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Setup Plugin parameter for integer from string value.
+ *
+ * in:
+ * param - a pointer to the param variable where we will render a parameter
+ * pname - a name of the parameter to compare
+ * name - a name of the parameter from parameter list
+ * value - a value to render
+ * out:
+ * True if parameter was parsed
+ * False if it was not the parameter required
+ */
+bool parse_param(int ¶m, const char *pname, const char *name, char *value, bool * err)
+{
+ // clear error flag when requested
+ if (err != NULL) *err = false;
+
+ if (value && bstrcasecmp(name, pname)){
+ /* convert str to integer */
+ param = atoi(value);
+ if (param == 0){
+ /* error in conversion */
+ DMsg2(DERROR, "Invalid %s parameter: %s\n", name, value);
+ // setup error flag
+ if (err != NULL) *err = true;
+ return false;
+ }
+ DMsg2(DINFO, "%s parameter: %d\n", name, param);
+
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Render and add a parameter for string value to alist.
+ * When alist is NULL (uninitialized) then it creates a new list to use.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * list - pointer to alist class to use
+ * pname - a name of the parameter to compare
+ * name - a name of the parameter from parameter list
+ * value - a value to render
+ * out:
+ * True if parameter was rendered
+ * False if it was not the parameter required
+ */
+bool add_param_str(alist **list, const char *pname, const char *name, char *value)
+{
+ POOLMEM *param;
+
+ if (bstrcasecmp(name, pname)){
+ if (!*list){
+ *list = New(alist(8, not_owned_by_alist));
+ }
+ param = get_pool_memory(PM_NAME);
+ Mmsg(param, "%s", value);
+ (*list)->append(param);
+ DMsg2(DDEBUG, "add param: %s=%s\n", name, value);
+ return true;
+ }
+ return false;
+}
--- /dev/null
+/*
+ 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.
+*/
+/*
+ *
+ * All rights reserved. IP transferred to Bacula Systems according to agreement.
+ *
+ * Common definitions and utility functions for Inteos plugins.
+ * Functions defines a common framework used in our utilities and plugins.
+ * Author: Radosław Korzeniewski, radekk@inteos.pl, Inteos Sp. z o.o.
+ */
+
+#ifndef _PLUGINLIB_H_
+#define _PLUGINLIB_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <ctype.h>
+
+#include "bacula.h"
+#include "fd_plugins.h"
+
+/* Pointers to Bacula functions used in plugins */
+extern bFuncs *bfuncs;
+extern bInfo *binfo;
+
+/* module definition */
+#ifndef PLUGMODULE
+#define PLUGMODULE "PluginLib::"
+#endif
+
+// #ifndef PLUGINPREFIX
+// #define PLUGINPREFIX PLUGMODULE
+// #endif
+
+/* size of different string or query buffers */
+#define BUFLEN 4096
+#define BIGBUFLEN 65536
+
+/* debug and messages functions */
+#define JMSG0(ctx,type,msg) \
+ if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, PLUGINPREFIX " " msg );
+#define JMSG1 JMSG
+#define JMSG(ctx,type,msg,var) \
+ if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, PLUGINPREFIX " " msg, var );
+#define JMSG2(ctx,type,msg,var1,var2) \
+ if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, PLUGINPREFIX " " msg, var1, var2 );
+#define JMSG3(ctx,type,msg,var1,var2,var3) \
+ if (ctx) bfuncs->JobMessage ( ctx, __FILE__, __LINE__, type, 0, PLUGINPREFIX " " msg, 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 );
+
+#define DMSG0(ctx,level,msg) \
+ if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, PLUGINPREFIX " " msg );
+#define DMSG1 DMSG
+#define DMSG(ctx,level,msg,var) \
+ if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, PLUGINPREFIX " " msg, var );
+#define DMSG2(ctx,level,msg,var1,var2) \
+ if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, PLUGINPREFIX " " msg, var1, var2 );
+#define DMSG3(ctx,level,msg,var1,var2,var3) \
+ if (ctx) bfuncs->DebugMessage ( ctx, __FILE__, __LINE__, level, PLUGINPREFIX " " msg, 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 );
+#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 );
+
+/* fixed debug level definitions */
+#define D1 1 /* debug for every error */
+#define DERROR D1
+#define D2 10 /* debug only important stuff */
+#define DINFO D2
+#define D3 200 /* debug for information only */
+#define DDEBUG D3
+#define D4 800 /* debug for detailed information only */
+#define DVDEBUG D4
+
+#define getBaculaVar(bvar,val) bfuncs->getBaculaValue(ctx, bvar, val);
+
+/* used for sanity check in plugin functions */
+#define ASSERT_CTX \
+ if (!ctx || !ctx->pContext || !bfuncs) \
+ { \
+ return bRC_Error; \
+ }
+
+/* defines for handleEvent */
+#define DMSG_EVENT_STR(event,value) DMSG2(ctx, DINFO, "%s value=%s\n", eventtype2str(event), NPRT((char *)value));
+#define DMSG_EVENT_CHAR(event,value) DMSG2(ctx, DINFO, "%s value='%c'\n", eventtype2str(event), (char)value);
+#define DMSG_EVENT_LONG(event,value) DMSG2(ctx, DINFO, "%s value=%ld\n", eventtype2str(event), (intptr_t)value);
+#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 BOOLSTR(b) (b?"True":"False")
+
+/*
+ * Common structure for key/pair values
+ */
+class key_pair : public SMARTALLOC
+{
+public:
+ POOL_MEM key;
+ POOL_MEM value;
+
+ key_pair() : key(PM_NAME), value(PM_MESSAGE) {};
+ key_pair(const char *k, const char *v)
+ {
+ pm_strcpy(key, k);
+ pm_strcpy(value, v);
+ };
+ ~key_pair() {};
+};
+
+const char *eventtype2str(bEvent *event);
+uint64_t pluglib_size_suffix(int disksize, char suff);
+uint64_t pluglib_size_suffix(double disksize, char suff);
+bRC pluglib_mkpath(bpContext* ctx, char* path, bool isfatal);
+
+/*
+ * Checks if plugin command points to our Plugin
+ *
+ * in:
+ * command - the plugin command used for backup/restore
+ * out:
+ * True - if it is our plugin command
+ * False - the other plugin command
+ */
+inline bool isourplugincommand(const char *pluginprefix, const char *command)
+{
+ /* check if it is our Plugin command */
+ if (strncmp(pluginprefix, command, strlen(pluginprefix)) == 0){
+ /* it is not our plugin prefix */
+ return true;
+ }
+ return false;
+}
+
+alist * plugutil_str_split_to_alist(const char * str, const char sep = '.');
+
+/* plugin parameters manipulation */
+bool render_param(POOLMEM **param, const char *pname, const char *fmt, const char *name, char *value);
+bool render_param(POOLMEM **param, const char *pname, const char *fmt, const char *name, int value);
+bool render_param(bool ¶m, const char *pname, const char *name, bool value);
+bool parse_param(bool ¶m, const char *pname, const char *name, char *value);
+bool parse_param(int ¶m, const char *pname, const char *name, char *value, bool *err = NULL);
+bool parse_param(POOL_MEM ¶m, const char *pname, const char *name, char *value);
+bool add_param_str(alist **list, const char *pname, const char *name, char *value);
+
+#endif /* _PLUGINLIB_H_ */
\ No newline at end of file
--- /dev/null
+/*
+ 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.
+*/
+/*
+ *
+ * All rights reserved. IP transferred to Bacula Systems according to agreement.
+ *
+ * Common definitions and utility functions for Inteos plugins.
+ * Functions defines a common framework used in our utilities and plugins.
+ * Author: Radosław Korzeniewski, radekk@inteos.pl, Inteos Sp. z o.o.
+ */
+
+#include "pluginlib.h"
+#include "unittests.h"
+
+bFuncs *bfuncs;
+bInfo *binfo;
+
+int main()
+{
+ Unittests pluglib_test("pluglib_test");
+ alist * list;
+ char * s;
+
+ // Pmsg0(0, "Initialize tests ...\n");
+
+ list = plugutil_str_split_to_alist("123456789");
+ ok(list != NULL, "default split");
+ ok(list->size() == 1, "expect single strings");
+ foreach_alist(s, list){
+ ok(strlen(s) == 9, "check element length");
+ }
+ delete list;
+
+ list = plugutil_str_split_to_alist("123.456");
+ ok(list != NULL, "split: 123.456");
+ ok(list->size() == 2, "expect two strings");
+ foreach_alist(s, list){
+ ok(strlen(s) == 3, "check element length");
+ }
+ delete list;
+
+ list = plugutil_str_split_to_alist("12345.56789.abcde");
+ ok(list != NULL, "split: 12345.56789.abcde");
+ ok(list->size() == 3, "expect three strings");
+ foreach_alist(s, list){
+ ok(strlen(s) == 5, "check element length");
+ }
+ delete list;
+
+ list = plugutil_str_split_to_alist("1.bacula..Eric.Kern");
+ ok(list != NULL, "split: 1.bacula..Eric.Kern");
+ ok(list->size() == 5, "expect three strings");
+ ok(strcmp((char*)list->first(), "1") == 0, "check element 1");
+ ok(strcmp((char*)list->next(), "bacula") == 0, "check element bacula");
+ ok(strlen((char*)list->next()) == 0, "check empty element");
+ ok(strcmp((char*)list->next(), "Eric") == 0, "check element Eric");
+ ok(strcmp((char*)list->next(), "Kern") == 0, "check element Kern");
+ delete list;
+
+ return report();
+}
--- /dev/null
+/*
+ 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 ptcomm.cpp
+ * @author Radosław Korzeniewski (radoslaw@korzeniewski.net)
+ * @brief This is a Bacula plugin library for interfacing with Metaplugin backend.
+ * @version 2.0.0
+ * @date 2020-11-20
+ *
+ * @copyright Copyright (c) 2020
+ */
+
+#include "ptcomm.h"
+#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
+ * libc implementation, unfortunately.
+ * use bsscanf for Bacula sscanf flavor
+ */
+#ifdef sscanf
+#undef sscanf
+#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).
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * out:
+ * true - when closed without a problem
+ * false - when got any error
+ */
+bool PTCOMM::close_extpipe(bpContext *ctx)
+{
+ int rc;
+
+ /* close expipe if used */
+ if (extpipe > 0){
+ rc = close(extpipe);
+ extpipe = -1;
+ if (rc != 0){
+ berrno be;
+ DMSG(ctx, DERROR, "Cannot close ExtPIPE. Err=%s\n", be.bstrerror());
+ JMSG(ctx, M_ERROR, "Cannot close ExtPIPE. Err=%s\n", be.bstrerror());
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+ * Terminate the connection represented by BPIPE object.
+ * it shows a debug and job messages when connection close is unsuccessful
+ * and when ctx is available only.
+ *
+ * in:
+ * bpContext - Bacula Plugin context required for debug/job messages to show,
+ * it could be NULL in this case no messages will be shown
+ * out:
+ * none
+ */
+void PTCOMM::terminate(bpContext *ctx)
+{
+ if (is_closed())
+ return;
+
+ pid_t worker_pid = bpipe->worker_pid;
+ int status = close_bpipe(bpipe);
+
+ bpipe = NULL; // indicte closed bpipe
+
+ if (status && ctx)
+ {
+ /* error during close */
+ berrno be;
+ DMSG(ctx, DERROR, "Error closing backend. Err=%s\n", be.bstrerror(status));
+ JMSG(ctx, M_ERROR, "Error closing backend. Err=%s\n", be.bstrerror(status));
+ }
+
+ if (worker_pid)
+ {
+ /* terminate the backend */
+ kill(worker_pid, SIGTERM);
+ }
+
+ if (extpipe > 0)
+ close_extpipe(ctx);
+};
+
+/**
+ * @brief Reads `nbytes` of data from backend into a buffer `buf`.
+ *
+ * This is a dedicated method for reading raw data from backend.
+ * It reads exact `nbytes` number of bytes and stores it at `buf`.
+ * It will not return until all requested data is ready or got error.
+ * You have to use it when you known exact number of bytes to read from
+ * the backend. The method handles errors and timeout reading data.
+ *
+ * @param ctx - for Bacula debug jobinfo messages
+ * @param buf - the memory buffer where we will read data
+ * @param nbytes - the exact number of bytes to read into `buf`
+ * @return true - when read was successful
+ * @return false - on any error
+ */
+bool PTCOMM::recvbackend_data(bpContext *ctx, char *buf, int32_t nbytes)
+{
+ int status;
+ int rbytes = 0;
+
+ _timeout.tv_sec = PTCOMM_DEFAULT_TIMEOUT;
+ _timeout.tv_usec = 0;
+
+ while (nbytes)
+ {
+ fd_set rfds;
+
+ FD_ZERO(&rfds);
+ FD_SET(rfd, &rfds);
+ FD_SET(efd, &rfds);
+
+ status = select(maxfd, &rfds, NULL, NULL, &_timeout);
+ if (status == 0)
+ {
+ // this means timeout waiting
+ f_error = true;
+ DMSG1(ctx, DERROR, "BPIPE read timeout=%d.\n", _timeout.tv_sec);
+ JMSG1(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE read timeout=%d.\n", _timeout.tv_sec);
+ return false;
+ }
+
+ // check if any data on error channel
+ if (FD_ISSET(efd, &rfds))
+ {
+ // do read of error channel
+ f_error = true;
+ status = read(efd, errmsg.c_str(), errmsg.size() - 1);
+ errmsg.c_str()[status] = '\0'; // terminate string
+ strip_trailing_junk(errmsg.c_str());
+ if (status < 0)
+ {
+ /* show any error during message read */
+ berrno be;
+ DMSG(ctx, DERROR, "BPIPE read error on error channel: ERR=%s\n", be.bstrerror());
+ JMSG(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE read error on error channel: ERR=%s\n", be.bstrerror());
+ } else {
+ // got data on error channel, report it
+ DMSG1(ctx, DERROR, "Backend reported error: %s\n", errmsg.c_str());
+ JMSG1(ctx, is_fatal() ? M_FATAL : M_ERROR, "Backend reported error: %s\n", errmsg.c_str());
+ }
+ }
+
+ // check if data descriptor is ready
+ if (FD_ISSET(rfd, &rfds))
+ {
+ // do read of data
+ status = read(rfd, buf + rbytes, nbytes);
+ if (status < 0)
+ {
+ /* show any error during data read */
+ berrno be;
+ f_error = true;
+ DMSG(ctx, DERROR, "BPIPE read error: ERR=%s\n", be.bstrerror());
+ JMSG(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE read error: ERR=%s\n", be.bstrerror());
+ return false;
+ }
+ if (status == 0){
+ /* the backend closed the connection without terminate signal 'T' */
+ f_error = true;
+ DMSG0(ctx, DERROR, "Backend closed the connection.\n");
+ JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "Backend closed the connection.\n");
+ return false;
+ }
+ nbytes -= status;
+ rbytes += status;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * @brief
+ *
+ * @param ctx
+ * @param buf
+ * @param nbytes
+ * @return true
+ * @return false
+ */
+bool PTCOMM::sendbackend_data(bpContext *ctx, POOLMEM *buf, int32_t nbytes)
+{
+ int status;
+ int wbytes = 0;
+
+ _timeout.tv_sec = PTCOMM_DEFAULT_TIMEOUT;
+ _timeout.tv_usec = 0;
+
+ while (nbytes)
+ {
+ fd_set rfds;
+ fd_set wfds;
+
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ FD_SET(efd, &rfds);
+ FD_SET(wfd, &wfds);
+
+ status = select(maxfd, &rfds, &wfds, NULL, &_timeout);
+ if (status == 0)
+ {
+ // this means timeout waiting
+ f_error = true;
+ DMSG1(ctx, DERROR, "BPIPE write timeout=%d.\n", _timeout.tv_sec);
+ JMSG1(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE write timeout=%d.\n", _timeout.tv_sec);
+ return false;
+ }
+
+ // check if any data on error channel
+ if (FD_ISSET(efd, &rfds))
+ {
+ // do read of error channel
+ f_error = true;
+ status = read(efd, errmsg.c_str(), errmsg.size());
+ if (status < 0)
+ {
+ /* show any error during message read */
+ berrno be;
+ DMSG(ctx, DERROR, "BPIPE read error on error channel: ERR=%s\n", be.bstrerror());
+ JMSG(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE read error on error channel: ERR=%s\n", be.bstrerror());
+ } else {
+ // got data on error channel, report it
+ DMSG1(ctx, DERROR, "Backend reported error: %s\n", errmsg.c_str());
+ JMSG1(ctx, is_fatal() ? M_FATAL : M_ERROR, "Backend reported error: %s\n", errmsg.c_str());
+ }
+ }
+
+ // check if data descriptor is ready
+ if (FD_ISSET(wfd, &wfds))
+ {
+ // do write of data
+ status = write(wfd, buf + wbytes, nbytes);
+ if (status < 0)
+ {
+ /* show any error during data write */
+ berrno be;
+ f_error = true;
+ DMSG(ctx, DERROR, "BPIPE write error: ERR=%s\n", be.bstrerror());
+ JMSG(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE write error: ERR=%s\n", be.bstrerror());
+ return false;
+ }
+ nbytes -= status;
+ wbytes += status;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * @brief Reads a protocol header from backend and return payload length.
+ *
+ * This method should be used at the start of every read from backend.
+ * It handles a full protocol chatting, i.e. error, warning and information
+ * messages besides EOD or termination.
+ *
+ * @param ctx - for Bacula debug jobinfo messages
+ * @param cmd - an expected command to read: `C` or `D`
+ * @return int32_t - the size of the packet payload
+ */
+int32_t PTCOMM::recvbackend_header(bpContext *ctx, char cmd)
+{
+ if (is_closed()){
+ DMSG0(ctx, DERROR, "BPIPE to backend is closed, cannot receive data.\n");
+ JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE to backend is closed, cannot receive data.\n");
+ return -1;
+ }
+
+ PTHEADER header;
+ bool workdone = false;
+
+ f_eod = f_error = f_fatal = false;
+ int32_t nbytes = sizeof(PTHEADER);
+
+ while (!workdone){
+ if (!recvbackend_data(ctx, (char*)&header, nbytes))
+ {
+ DMSG0(ctx, DERROR, "PTCOMM cannot get packet header from backend.\n");
+ JMSG0(ctx, M_FATAL, "PTCOMM cannot get packet header from backend.\n");
+ f_eod = f_error = f_fatal = true;
+ return -1;
+ }
+ DMSG(ctx, DDEBUG, "RECV: %c\n", header.status);
+
+ /* check for protocol status */
+ if (header.status == 'F'){
+ /* signal EOD */
+ f_eod = true;
+ return 0;
+ }
+
+ if (header.status == 'T'){
+ /* backend signaled a connection termination */
+ terminate(ctx);
+ return 0;
+ }
+
+ // other packet commands require data
+ header.length[6] = 0; /* end of string */
+
+ // convert packet length from ASCII to binary
+ int32_t msglen = atoi(header.length);
+
+ if (header.status == 'C' || header.status == 'D')
+ {
+ if (header.status != cmd)
+ {
+ DMSG2(ctx, DERROR, "Protocol error. Expected packet: %c got: %c\n", cmd, header.status);
+ JMSG2(ctx, M_FATAL, "Protocol error. Expected packet: %c got: %c\n", cmd, header.status);
+ return -1;
+ }
+
+ // this means no additional handling required
+ return msglen;
+ }
+
+ // need a space for nul and newline char at the end of the message
+ errmsg.check_size(msglen + 2);
+
+ // read the rest of the package
+ if (!recvbackend_data(ctx, errmsg.c_str(), msglen))
+ {
+ DMSG0(ctx, DERROR, "PTCOMM cannot get message from backend.\n");
+ JMSG0(ctx, M_FATAL, "PTCOMM cannot get message from backend.\n");
+ return -1;
+ }
+
+ // ensure error message is terminated with newline and
+ // terminated with standard c-string nul
+ errmsg.c_str()[msglen] = errmsg.c_str()[msglen - 1] != '\n' ? '\n' : '\0';
+ errmsg.c_str()[msglen + 1] = '\0';
+
+ switch (header.status)
+ {
+ /* backend signal errors */
+ case 'E':
+ case 'A':
+ /* setup error flags */
+ f_error = true;
+ f_fatal = header.status == 'A';
+
+ /* show error to Bacula */
+ DMSG(ctx, DERROR, "Backend Error: %s", errmsg.c_str());
+ JMSG(ctx, f_fatal ? M_FATAL : M_ERROR, "%s", errmsg.c_str());
+ workdone = true;
+ break;
+
+ // handle warning and info messages below
+ case 'W':
+ // handle warning message
+ DMSG(ctx, DERROR, "%s", errmsg.c_str());
+ JMSG(ctx, M_WARNING, "%s", errmsg.c_str());
+ continue;
+
+ case 'I':
+ // handle information message
+ DMSG(ctx, DINFO, "%s", errmsg.c_str());
+ JMSG(ctx, M_INFO, "%s", errmsg.c_str());
+ continue;
+
+ default:
+ DMSG1(ctx, DERROR, "Protocol error. Unknown packet: %c got: %c\n", header.status);
+ JMSG1(ctx, M_FATAL, "Protocol error. Unknown packet: %c got: %c\n", header.status);
+ return -1;
+ }
+ }
+
+ return -1;
+}
+
+/**
+ * @brief Handles a receive (read) packet header.
+ *
+ * @param ctx bpContext - for Bacula debug jobinfo messages
+ * @param cmd
+ * @return int32_t
+ */
+int32_t PTCOMM::handle_read_header(bpContext *ctx, char cmd)
+{
+ // first read is the packet header where we will have info about data
+ // which is sent to us; the packet header is 8 chars/bytes length fixed
+ // nbytes shows how many bytes we expects to read
+ int32_t length = recvbackend_header(ctx, cmd);
+ if (length < 0)
+ {
+ // error
+ DMSG0(ctx, DERROR, "PTCOMM cannot get packet header from backend.\n");
+ JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "PTCOMM cannot get packet header from backend.\n");
+ f_eod = f_error = f_fatal = true;
+ return -1;
+ }
+
+ return length;
+}
+
+/**
+ * @brief Handles a payload (message) which comes after the header.
+ *
+ * @param ctx bpContext - for Bacula debug jobinfo messages
+ * @param buf - the POOLMEM buffer we will read data
+ * @param nbytes - the size of the fized buffer
+ * @return int32_t
+ * 0: when backend sent signal, i.e. EOD or Term
+ * -1: when we've got any error; the function will report it to Bacula when
+ * ctx is not NULL
+ * <n>: the size of received message
+ */
+int32_t PTCOMM::handle_payload(bpContext *ctx, char *buf, int32_t nbytes)
+{
+ // handle raw data read as payload
+ if(!recvbackend_data(ctx, buf, nbytes))
+ {
+ // error
+ DMSG0(ctx, DERROR, "PTCOMM cannot get packet payload from backend.\n");
+ JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "PTCOMM cannot get packet payload from backend.\n");
+ f_eod = f_error = f_fatal = true;
+ return -1;
+ }
+
+ return nbytes;
+}
+
+/**
+ * @brief Receive a packet from the backend.
+ *
+ * The caller expects a packet of a particular type (`cmd`) and we return
+ * from function only when we will receive this kind of packet or get any error.
+ * The `buf` will be extended if message extent current buffer size.
+ *
+ * @param ctx bpContext - for Bacula debug jobinfo messages
+ * @param cmd the packet type expected
+ * @param buf the POOL_MEM buffer we will read data
+ * @return int32_t
+ * 0: when backend sent signal, i.e. EOD or Term
+ * -1: when we've got any error; the function will report it to Bacula when
+ * ctx is not NULL
+ * <n>: the size of received message
+ */
+int32_t PTCOMM::recvbackend(bpContext *ctx, char cmd, POOL_MEM &buf)
+{
+ // handle header
+ int32_t length = handle_read_header(ctx, cmd);
+ if (length < 0)
+ return -1;
+
+ // handle data payload
+ if (length > 0)
+ {
+ // check requested buffer size
+ buf.check_size(length);
+ return handle_payload(ctx, buf.c_str(), length);
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Receive a packet from the backend.
+ *
+ * The caller expects a packet of a particular type (`cmd`) and we return
+ * from function when we will receive this kind of packet or get any error.
+ * The `buf` is fixed size, so it won't be extended for larger messages.
+ * In this case you have to make more calls to get all data.
+ *
+ * @param ctx bpContext - for Bacula debug jobinfo messages
+ * @param cmd - the packet type expected
+ * @param buf - the POOLMEM buffer we will read data
+ * @param bufsize - the size of the fized buffer
+ * @return int32_t
+ * 0: when backend sent signal, i.e. EOD or Term
+ * -1: when we've got any error; the function will report it to Bacula when
+ * ctx is not NULL
+ * <n>: the size of received message
+ */
+int32_t PTCOMM::recvbackend_fixed(bpContext *ctx, char cmd, char *buf, int32_t bufsize)
+{
+ int32_t length = remaininglen;
+
+ if (!f_cont)
+ {
+ // handle header
+ length = handle_read_header(ctx, cmd);
+ if (length < 0)
+ return -1;
+ }
+
+ // handle data payload
+ if (length > 0)
+ {
+ // we will need subsequent call to handle remaining data only when `buf` to short
+ f_cont = length > bufsize;
+ int32_t nbytes = f_cont * bufsize + (!f_cont) * length;
+ remaininglen = f_cont * (length - bufsize);
+ return handle_payload(ctx, buf, nbytes);
+ }
+
+ return 0;
+}
+
+/*
+ * Sends packet to the backend.
+ * The protocol allows sending no more than 999999 bytes of data in one packet.
+ * If you require to send more data you have to split it in more packages, and
+ * backend has to assemble it into a larger chunk of data.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * cmd - the packet status to send
+ * buf - the packet contents
+ * len - the length of the contents
+ * out:
+ * -1 - when encountered any error
+ * <n> - the number of bytes sent, success
+ */
+int32_t PTCOMM::sendbackend(bpContext *ctx, char cmd, POOLMEM *buf, int32_t len)
+{
+ int status;
+ PTHEADER *header;
+ PTHEADER myheader;
+
+ if (is_closed()){
+ DMSG0(ctx, DERROR, "BPIPE to backend is closed, cannot send data.\n");
+ JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE to backend is closed, cannot send data.\n");
+ return -1;
+ }
+
+ if (len > 999999){
+ /* message length too long, cannot send it */
+ DMSG(ctx, DERROR, "Message length %i too long, cannot send data.\n", len);
+ JMSG(ctx, M_FATAL, "Message length %i too long, cannot send data.\n", len);
+ return -1;
+ }
+
+#ifdef NEED_REVIEW
+ // The code at NEED_REVIEW uses POOLMEM abufhead reserved space for
+ // packet header rendering in the same way as bsock.c do. The code was tested
+ // and is working fine. No memory leakage or corruption encountered.
+ // The only pros for this code is a single fwrite call for a whole message
+ // instead of two fwrites (header + data) for a standard method.
+ if (buf){
+ // we will prepare POOLMEM for sending data so we can render header here
+ header = (PTHEADER*) (buf - sizeof(PTHEADER));
+ } else {
+ // we will send header only
+ header = &myheader;
+ }
+#else
+ header = &myheader;
+#endif
+ header->status = cmd;
+ DMSG2(ctx, DDEBUG, "SENT: %c %s\n", header->status, buf ? buf : "");
+ if (bsnprintf(header->length, sizeof(PTHEADER), "%06i", len) != 6){
+ /* problem rendering packet header */
+ DMSG0(ctx, DERROR, "Problem rendering packet header for command.\n");
+ JMSG0(ctx, M_FATAL, "Problem rendering packet header for command.\n");
+ return -1;
+ }
+ header->length[6] = '\n';
+
+#ifdef NEED_REVIEW
+ status = sendbackend_data(ctx, (char*)header, len + sizeof(PTHEADER));
+ status -= sizeof(PTHEADER);
+#else
+ status = write(wfd, header, sizeof(PTHEADER));
+ if (buf){
+ /* we have some data or command to send */
+ status = write(wfd, buf, len);
+ }
+#endif
+ if (status < 0)
+ {
+ // error
+ DMSG0(ctx, DERROR, "PTCOMM cannot write packet to backend.\n");
+ JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "PTCOMM cannot write packet to backend.\n");
+ f_eod = f_error = f_fatal = true;
+ return -1;
+ }
+
+#ifdef NEED_REVIEW
+ // correct real payload data size
+ status -= sizeof(PTHEADER);
+#endif
+
+ return status;
+}
+
+/**
+ * @brief Reads the next command message from the backend communication channel.
+ *
+ * It expects the command, so returned data will be null terminated string
+ * stripped on any unwanted junk, i.e. '\n' or 'space'.
+ *
+ * @param ctx bpContext - for Bacula debug and jobinfo messages
+ * @param buf buffer allocated for command
+ * @return int32_t
+ * -1 - when encountered any error
+ * 0 - when backend sent signal, i.e. EOD or Term
+ * <n> - the number of bytes received, success
+ */
+int32_t PTCOMM::read_command(bpContext *ctx, POOL_MEM &buf)
+{
+ int32_t status = recvbackend(ctx, 'C', buf);
+ if (status > 0)
+ {
+ /* mark end of string because every command is a string */
+ buf.c_str()[status] = '\0';
+ /* strip any junk in command like '\n' or trailing spaces */
+ strip_trailing_junk(buf.c_str());
+ }
+
+ return status;
+}
+
+/*
+ * Reads the next data message from the backend.
+ * The number of bytes received will not exceed the buffer length even when
+ * backend will send more data. In this case next call to read_data() will
+ * return the next part of the message.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * buf - buffer allocated for data
+ * len - the size of the allocated buffer
+ * out:
+ * -1 - when encountered any error
+ * 0 - when backend sent signal, i.e. EOD or Term
+ * <n> - the number of bytes received, success
+ * buf - the command string received from backend
+ */
+int32_t PTCOMM::read_data(bpContext *ctx, POOL_MEM &buf)
+{
+ int32_t status;
+
+ if (extpipe > 0){
+ status = read(extpipe, buf.c_str(), buf.size());
+ } else {
+ status = recvbackend(ctx, 'D', buf);
+ }
+
+ return status;
+}
+
+/*
+ * Reads the next data message from the backend.
+ * The number of bytes received will not exceed the buffer length even when
+ * backend will send more data. In this case next call to read_data() will
+ * return the next part of the message.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * buf - buffer allocated for data
+ * len - the size of the allocated buffer
+ * out:
+ * -1 - when encountered any error
+ * 0 - when backend sent signal, i.e. EOD or Term
+ * <n> - the number of bytes received, success
+ * buf - the command string received from backend
+ */
+int32_t PTCOMM::read_data_fixed(bpContext *ctx, char *buf, int32_t len)
+{
+ int32_t status;
+
+ if (extpipe > 0){
+ status = read(extpipe, buf, len);
+ } else {
+ status = recvbackend_fixed(ctx, 'D', buf, len);
+ }
+
+ return status;
+}
+
+/*
+ * Receive an acknowledge from backend (the EOD package).
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * out:
+ * True when acknowledge received
+ * False when got any error
+ */
+bool PTCOMM::read_ack(bpContext *ctx)
+{
+ POOL_MEM buf(PM_FNAME);
+
+ if (recvbackend(ctx, 'F', buf) == 0 && f_eod)
+ {
+ f_eod = false;
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Sends a command to the backend.
+ * The command has to be a nul terminated string.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * buf - a message buffer contains command to send
+ * out:
+ * -1 - when encountered any error
+ * <n> - the number of bytes sent, success
+ */
+int32_t PTCOMM::write_command(bpContext *ctx, POOLMEM *buf)
+{
+ int32_t len;
+ len = buf ? strlen(buf) : 0;
+ return sendbackend(ctx, 'C', buf, len);
+}
+
+/*
+ * Sends a raw data to backend.
+ * The length of the data should not exceed max packet size which is 999999 Byes.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * buf - a message buffer contains data to send
+ * len - the length of the data to send
+ * out:
+ * -1 - when encountered any error
+ * <n> - the number of bytes sent, success
+ */
+int32_t PTCOMM::write_data(bpContext *ctx, POOLMEM *buf, int32_t len)
+{
+ int32_t status;
+
+ if (extpipe > 0){
+ status = write(extpipe, buf, len);
+ } else {
+ status = sendbackend(ctx, 'D', buf, len);
+ }
+ return status;
+}
+
+/*
+ * Sends acknowledge to the backend which consist of the following flow:
+ * -> EOD
+ * <- OK
+ * or
+ * <- Error
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * out:
+ * True when acknowledge sent successful
+ * False when got any error
+ */
+bool PTCOMM::send_ack(bpContext *ctx)
+{
+ POOL_MEM buf(PM_FNAME);
+
+ if (signal_eod(ctx) < 0){
+ // error
+ return false;
+ }
+
+ if (read_command(ctx, buf) < 0){
+ // error
+ return false;
+ }
+
+ // check if backend response with OK
+ if (bstrcmp(buf.c_str(), "OK")){
+ // great ACk confirmed
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * @brief Send a handshake procedure to the backend using PLUGINNAME and PLUGINAPI.
+ *
+ * @param ctx bpContext - for Bacula debug and jobinfo messages
+ * @param pluginname - the plugin name part of the handshake
+ * @param pluginapi - the protocol version of the plugin
+ * @return true - when handshake successful
+ * @return false - when not
+ */
+bool PTCOMM::handshake(bpContext *ctx, const char *pluginname, const char * pluginapi)
+{
+ POOL_MEM cmd(PM_FNAME);
+
+ Mmsg(cmd, "Hello %s %s\n", pluginname, pluginapi);
+ int32_t status = write_command(ctx, cmd);
+ if (status > 0){
+ status = read_command(ctx, cmd);
+ if (status > 0){
+ if (bstrcmp(cmd.c_str(), "Hello Bacula")){
+ /* handshake successful */
+ return true;
+ } else {
+ DMSG(ctx, DERROR, "Wrong backend response to Hello command, got: %s\n", cmd.c_str());
+ JMSG(ctx, is_fatal() ? M_FATAL : M_ERROR, "Wrong backend response to Hello command, got: %s\n", cmd.c_str());
+ }
+ }
+ }
+
+ return false;
+}
--- /dev/null
+/*
+ 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.
+*/
+/**
+ * This is a process communication lowlevel library for Bacula plugin.
+ * Author: Radoslaw Korzeniewski, radekk@inteos.pl, Inteos Sp. z o.o.
+ */
+
+#ifndef _PTCOMM_H_
+#define _PTCOMM_H_
+
+#include "pluginlib.h"
+
+#define PTCOMM_DEFAULT_TIMEOUT 300 // timeout waiting for data 15 min, it should be enough
+
+/*
+ * The protocol packet header.
+ * Every packet exchanged between Plugin and Backend will have a special header
+ * which allow to perfectly synchronize data exchange mitigating the risk
+ * of a deadlock, where both ends will wait for a data and no one wants to
+ * send it to the other end.
+ * The protocol implements a single char packet status which could be:
+ * D - data packet
+ * C - command packet
+ * E - error packet
+ * F - EOD packet
+ * T - terminate connection
+ * W - warning message
+ * I - information message
+ * A - fatal error message (abort)
+ * The length is an ascii coded decimal trailed by a "newline" char - '\n'.
+ * So, a packet header could be rendered as: 'C000012\n'
+ */
+struct PTHEADER
+{
+ char status;
+ char length[7];
+};
+
+/*
+ * This is a low-level transport communication class which handles all bits and
+ * bytes of the protocol.
+ * The class express a high-level methods for low-level transport protocol.
+ * It handles a communication channel (bpipe) with backend execution and
+ * termination. The external data exchange using named pipes or local files is
+ * handled by this class too.
+ */
+class PTCOMM : public SMARTALLOC
+{
+private:
+ BPIPE *bpipe; // this is our bpipe to communicate with backend */
+ int rfd; //
+ int wfd; //
+ int efd; //
+ int maxfd; //
+ POOL_MEM errmsg; // message buffer for error string */
+ int extpipe; // set when data blast is performed using external pipe/file */
+ POOL_MEM extpipename; // name of the external pipe/file for restore */
+ bool f_eod; // the backend signaled EOD */
+ bool f_error; // the backend signaled an error */
+ bool f_fatal; // the backend signaled a fatal error */
+ bool f_cont; // when we are reading next part of data packet */
+ bool abort_on_error; // abort on error flag */
+ int32_t remaininglen; // the number of bytes to read when `f_cont` is true
+ struct timeval _timeout; //
+
+protected:
+ bool recvbackend_data(bpContext *ctx, char *buf, int32_t nbytes);
+ bool sendbackend_data(bpContext *ctx, char *buf, int32_t nbytes);
+
+ int32_t recvbackend_header(bpContext *ctx, char cmd);
+ int32_t handle_read_header(bpContext *ctx, char cmd);
+ int32_t handle_payload(bpContext *ctx, char *buf, int32_t nbytes);
+
+ int32_t recvbackend(bpContext *ctx, char cmd, POOL_MEM &buf);
+ int32_t recvbackend_fixed(bpContext *ctx, char cmd, char *buf, int32_t bufsize);
+
+ int32_t sendbackend(bpContext *ctx, char cmd, POOLMEM *buf, int32_t len);
+
+public:
+ PTCOMM() :
+ bpipe(NULL),
+ rfd(0),
+ wfd(0),
+ efd(0),
+ maxfd(0),
+ errmsg(PM_MESSAGE),
+ extpipe(-1),
+ extpipename(PM_FNAME),
+ f_eod(false),
+ f_error(false),
+ f_fatal(false),
+ f_cont(false),
+ abort_on_error(false),
+ remaininglen(0)
+ {}
+#if __cplusplus > 201103L
+ PTCOMM(PTCOMM &) = delete;
+ PTCOMM(PTCOMM &&) = delete;
+#endif
+ ~PTCOMM() { terminate(NULL); }
+
+ bool handshake(bpContext *ctx, const char *pluginname, const char *pluginapi);
+
+ int32_t read_command(bpContext *ctx, POOL_MEM &buf);
+ int32_t read_data(bpContext *ctx, POOL_MEM &buf);
+ int32_t read_data_fixed(bpContext *ctx, char *buf, int32_t len);
+
+ int32_t write_command(bpContext *ctx, char *buf);
+
+ /**
+ * @brief Sends a command to the backend.
+ *
+ * @param ctx bpContext - for Bacula debug and jobinfo messages
+ * @param buf a message buffer contains command to send
+ * @return int32_t
+ * -1 - when encountered any error
+ * <n> - the number of bytes sent, success
+ */
+ int32_t write_command(bpContext *ctx, POOL_MEM &buf) { return write_command(ctx, buf.addr()); }
+ int32_t write_data(bpContext *ctx, char *buf, int32_t len);
+
+ bool read_ack(bpContext *ctx);
+ bool send_ack(bpContext *ctx);
+
+ /**
+ * @brief Signals en error to the backend.
+ *
+ * The buf, when not NULL, can hold an error string sent do the backend.
+ *
+ * @param ctx - for Bacula debug and jobinfo messages
+ * @param buf - when not NULL should consist of an error string
+ * - when NULL, no error string sent to the backend
+ * @return int32_t
+ * -1 - when encountered any error
+ * <n> - the number of bytes sent, success
+ */
+ inline int32_t signal_error(bpContext *ctx, POOLMEM *buf)
+ {
+ int32_t len = buf ? strlen(buf) : 0;
+ return sendbackend(ctx, 'E', buf, len);
+ }
+
+ POOLMEM *get_error(bpContext *ctx);
+
+ /**
+ * @brief Signals EOD to backend.
+ *
+ * @param ctx bpContext - for Bacula debug and jobinfo messages
+ * @return int32_t
+ * -1 - when encountered any error
+ * <n> - the number of bytes sent, success
+ */
+ inline int32_t signal_eod(bpContext *ctx) { return sendbackend(ctx, 'F', NULL, 0); }
+
+ /**
+ * @brief Signal end of communication to the backend.
+ * The backend should close the connection after receiving this packet.
+ *
+ * @param ctx bpContext - for Bacula debug and jobinfo messages
+ * @return int32_t
+ * -1 - when encountered any error
+ * <n> - the number of bytes sent, success
+ */
+ inline int32_t signal_term(bpContext *ctx) { return sendbackend(ctx, 'T', NULL, 0); }
+
+ void terminate(bpContext *ctx);
+
+ /**
+ * @brief Returns a backend PID if available.
+ * I'm using an arthrymetic way to make a conditional value return;
+ *
+ * @return int backend PID - when backend available; -1 - when backend is unavailable
+ */
+ int get_backend_pid() { return (bpipe != NULL) * bpipe->worker_pid - (bpipe == NULL); }
+
+ /**
+ * @brief Sets a BPIPE object for our main communication channel.
+ *
+ * @param bp object, we do not check for NULL here
+ */
+ inline void set_bpipe(BPIPE *bp)
+ {
+ bpipe = bp;
+ rfd = fileno(bpipe->rfd);
+ wfd = fileno(bpipe->wfd);
+ efd = fileno(bpipe->efd);
+ maxfd = MAX(rfd, wfd);
+ maxfd = MAX(maxfd, efd) + 1;
+ }
+
+ /**
+ * @brief Sets a FILE descriptor used as external pipe during backup and restore.
+ *
+ * @param ep a FILE* descriptor used during
+ */
+ inline void set_extpipe(int ep) { extpipe = ep; }
+
+ /**
+ * @brief Sets an external pipe name for restore.
+ *
+ * @param epname - external pipe name
+ */
+ inline void set_extpipename(char *epname) { pm_strcpy(extpipename, epname); }
+ bool close_extpipe(bpContext *ctx);
+
+ /**
+ * @brief Checks if connection is open and we can use a bpipe object for communication.
+ *
+ * @return true if connection is available
+ * @return false if connection is closed and we can't use bpipe object
+ */
+ inline bool is_open() { return bpipe != NULL; }
+
+ /**
+ * @brief Checks if connection is closed and we can't use a bpipe object for communication.
+ *
+ * @return true if connection is closed and we can't use bpipe object
+ * @return false if connection is available
+ */
+ inline bool is_closed() { return bpipe == NULL; }
+
+ /**
+ * @brief Checks if backend sent us some error, backend error message is flagged on f_error.
+ *
+ * @return true when last packet was an error
+ * @return false when no error packets was received
+ */
+ inline bool is_error() { return f_error || f_fatal; }
+
+ /**
+ * @brief Checks if backend sent us fatal error, backend error message is flagged on f_fatal.
+ *
+ * @return true when last packet was a fatal error
+ * @return false when no fatal error packets was received
+ */
+ inline bool is_fatal() { return f_fatal || (f_error && abort_on_error); }
+
+ /**
+ * @brief Checks if backend signaled EOD, eod from backend is flagged on f_eod.
+ *
+ * @return true when backend signaled EOD on last packet
+ * @return false when backend did not signal EOD
+ */
+ inline bool is_eod() { return f_eod; }
+
+ /**
+ * @brief Clears the EOD from backend flag, f_eod.
+ * The eod flag is set when EOD message received from backend and not cleared
+ * until next recvbackend() call.
+ */
+ void clear_eod() { f_eod = false; }
+
+ /**
+ * @brief Set the abort on error flag
+ */
+ inline void set_abort_on_error() { abort_on_error = true; }
+
+ /**
+ * @brief Clears the abort on error flag.
+ */
+ inline void clear_abort_on_error() { abort_on_error = false; }
+
+ /**
+ * @brief return abort on error flag status
+ *
+ * @return true if flag is set
+ * @return false if flag is not set
+ */
+ bool is_abort_on_error() { return abort_on_error; }
+};
+
+#endif /* _PTCOMM_H_ */