]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
Add metadata support for metaplugin.
authorRadosław Korzeniewski <radoslaw@korzeniewski.net>
Thu, 11 Mar 2021 10:59:46 +0000 (11:59 +0100)
committerEric Bollengier <eric@baculasystems.com>
Thu, 24 Mar 2022 08:03:00 +0000 (09:03 +0100)
bacula/src/plugins/fd/pluginlib/metaplugin.cpp
bacula/src/plugins/fd/pluginlib/metaplugin.h
bacula/src/plugins/fd/pluginlib/smartalist.h
bacula/src/plugins/fd/pluginlib/test_metaplugin_backend.c

index 7e60b4683a2231b9ae1d71019773d54fdbe56a3c..02fef06b1d9c92089aa075e010584aee4514e27f 100644 (file)
@@ -64,6 +64,7 @@ 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);
+static bRC metadataRestore(bpContext *ctx, struct meta_pkt *mp);
 
 /* Pointers to Bacula functions */
 bFuncs *bfuncs = NULL;
@@ -92,6 +93,7 @@ static pFuncs pluginFuncs =
    NULL,                        /* No restore file list */
    NULL,                        /* No checkStream */
    queryParameter,
+   metadataRestore,
 };
 
 #ifdef __cplusplus
@@ -175,7 +177,8 @@ METAPLUGIN::METAPLUGIN(bpContext *bpctx) :
       acldatalen(0),
       acldata(PM_MESSAGE),
       xattrdatalen(0),
-      xattrdata(PM_MESSAGE)
+      xattrdata(PM_MESSAGE),
+      metadatas_list(10, true)
 {
    /* 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 */
@@ -770,6 +773,7 @@ bRC METAPLUGIN::send_parameters(bpContext *ctx, char *command)
       "regress_backup_plugin_objects",
       "regress_backup_other_file",
       "regress_error_backup_abort",
+      "regress_metadata_support",
       NULL,
    };
 #endif
@@ -803,11 +807,9 @@ bRC METAPLUGIN::send_parameters(bpContext *ctx, char *command)
       }
 
 #ifdef DEVELOPER
-      if (!found)
-      {
+      if (!found){
          // now handle regression tests commands
-         for (int a = 0; regress_valid_params[a] != NULL; a++ )
-         {
+         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;
@@ -1477,6 +1479,77 @@ bRC METAPLUGIN::perform_read_xattr(bpContext *ctx)
    return bRC_OK;
 }
 
+/**
+ * @brief Reads metadata info from backend and adds it as a metadata packet.
+ *
+ * @param ctx for Bacula debug and jobinfo messages
+ * @param type detected Metadata type
+ * @param sp save packet
+ * @return bRC bRC_OK when success, bRC_Error when some error
+ */
+bRC METAPLUGIN::perform_read_metadata_info(bpContext *ctx, metadata_type type, struct save_pkt *sp)
+{
+   POOL_MEM data(PM_MESSAGE);
+
+   DMSG0(ctx, DINFO, "perform_read_metadata_info\n");
+
+   int len = backend.ctx->read_data(ctx, data);
+   if (len < 0){
+      DMSG1(ctx, DERROR, "Cannot read METADATA(%i) information from backend.\n", type);
+      return bRC_Error;
+   }
+
+   DMSG1(ctx, DINFO, "read METADATA info len: %i\n", len);
+   if (!backend.ctx->read_ack(ctx)){
+      /* should get EOD */
+      DMSG0(ctx, DERROR, "Protocol error, should get EOD.\n");
+      return bRC_Error;
+   }
+
+   // Bacula API for metadata requires that a plugin
+   // handle metadata buffer allocation
+   POOLMEM *ptr = (POOLMEM *)bmalloc(len);
+   memcpy(ptr, data.addr(), len);
+
+   // add it to the list for reference to not lot it
+   metadatas_list.append(ptr);
+   metadatas.add_packet(type, len, ptr);
+   sp->plug_meta = &metadatas;
+
+   return bRC_OK;
+}
+
+/**
+ * @brief Does metadata command scan and map to metadata types.
+ *
+ * @param cmd a command string read from backend
+ * @return metadata_type returned from map
+ */
+metadata_type METAPLUGIN::scan_metadata_type(const POOL_MEM &cmd)
+{
+   DMSG1(ctx, DDEBUG, "scan_metadata_type checking: %s\n", cmd.c_str());
+   for (int i = 0; plugin_metadata_map[i].command != NULL; i++)
+   {
+      if (bstrcmp(cmd.c_str(), plugin_metadata_map[i].command)){
+         DMSG2(ctx, DDEBUG, "match: %s => %d\n", plugin_metadata_map[i].command, plugin_metadata_map[i].type);
+         return plugin_metadata_map[i].type;
+      }
+   }
+
+   return plugin_meta_invalid;
+}
+
+const char * METAPLUGIN::prepare_metadata_type(metadata_type type)
+{
+   for (int i = 0; plugin_metadata_map[i].command != NULL; i++){
+      if (plugin_metadata_map[i].type == type){
+         return plugin_metadata_map[i].command;
+      }
+   }
+
+   return "METADATA_STREAM\n";
+}
+
 /*
  * Sends ACL data from restore stream to backend.
  * TODO: The method has a limitation and accept a single xacl_pkt call for
@@ -1532,7 +1605,6 @@ bRC METAPLUGIN::perform_write_acl(bpContext* ctx, xacl_pkt* xacl)
  */
 bRC METAPLUGIN::perform_write_xattr(bpContext* ctx, xacl_pkt* xacl)
 {
-   int rc;
    POOL_MEM cmd(PM_FNAME);
 
    if (xacl->count > 0){
@@ -1541,7 +1613,7 @@ bRC METAPLUGIN::perform_write_xattr(bpContext* ctx, xacl_pkt* xacl)
       backend.ctx->write_command(ctx, cmd.c_str());
       /* send xattrs data */
       DMSG1(ctx, DINFO, "writeXATTR: %i\n", xacl->count);
-      rc = backend.ctx->write_data(ctx, xacl->content, xacl->count);
+      int rc = backend.ctx->write_data(ctx, xacl->content, xacl->count);
       if (rc < 0){
          /* got some error */
          return bRC_Error;
@@ -1551,6 +1623,7 @@ bRC METAPLUGIN::perform_write_xattr(bpContext* ctx, xacl_pkt* xacl)
          return bRC_Error;
       }
    }
+
    return bRC_OK;
 }
 
@@ -1581,14 +1654,12 @@ bRC METAPLUGIN::perform_read_metadata(bpContext *ctx)
       if (backend.ctx->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))
-         {
+         if (scan_parameter_str(cmd, "FNAME:", fname)){
             /* got FNAME: */
             nextfile = true;
             return bRC_OK;
          }
-         if (scan_parameter_str(cmd, "PLUGINOBJ:", fname))
-         {
+         if (scan_parameter_str(cmd, "PLUGINOBJ:", fname)){
             /* got Plugin Object header */
             nextfile = true;
             pluginobject = true;
@@ -1644,43 +1715,36 @@ bRC METAPLUGIN::perform_read_pluginobject(bpContext *ctx, struct save_pkt *sp)
    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 (backend.ctx->read_command(ctx, cmd) > 0)
-      {
+   while (true){
+      if (backend.ctx->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))
-         {
+         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))
-         {
+         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))
-         {
+         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))
-         {
+         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 (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());
@@ -1710,7 +1774,6 @@ bRC METAPLUGIN::perform_read_pluginobject(bpContext *ctx, struct save_pkt *sp)
    }
 
    return bRC_Error;
-
 }
 
 /*
@@ -1875,11 +1938,15 @@ bRC METAPLUGIN::startBackupFile(bpContext *ctx, struct save_pkt *sp)
    sp->fname = fname.c_str();
 
    if (!pluginobject){
-      // here we handle standard metadata
+      // here we handle metadata information
       reqparams--;
 
-      while (backend.ctx->read_command(ctx, cmd) > 0)
-      {
+      // ensure clear state for metadatas
+      sp->plug_meta = NULL;
+      metadatas.reset();
+      metadatas_list.destroy();
+
+      while (backend.ctx->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;
@@ -1912,45 +1979,55 @@ bRC METAPLUGIN::startBackupFile(bpContext *ctx, struct save_pkt *sp)
             reqparams--;
             continue;
          }
-         if (bsscanf(cmd.c_str(), "TSTAMP:%ld %ld %ld", &sp->statp.st_atime, &sp->statp.st_mtime, &sp->statp.st_ctime) == 3)
-         {
+         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 (scan_parameter_str(cmd, "LSTAT:", lname) == 1)
-         {
+         if (scan_parameter_str(cmd, "LSTAT:", lname) == 1) {
             sp->link = lname.c_str();
             reqparams--;
             DMSG(ctx, DINFO, "LSTAT:%s\n", lname.c_str());
             continue;
          }
          POOL_MEM tmp(PM_FNAME);
-         if (scan_parameter_str(cmd, "PIPE:", tmp)){
+         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)
-            {
+            if (extpipe > 0) {
                DMSG0(ctx, DINFO, "ExtPIPE file available.\n");
                backend.ctx->set_extpipe(extpipe);
                pm_strcpy(tmp, "OK\n");
                backend.ctx->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, backend.ctx->is_abort_on_error() ? M_FATAL : M_ERROR, "ExtPIPE file open error! Err=%s\n", be.bstrerror());
+               JMSG(ctx, backend.ctx->jmsg_err_level(), "ExtPIPE file open error! Err=%s\n", be.bstrerror());
                pm_strcpy(tmp, "Err\n");
                backend.ctx->signal_error(ctx, tmp.c_str());
                return bRC_Error;
             }
+            continue;
+         }
+         metadata_type mtype = scan_metadata_type(cmd);
+         if (mtype != plugin_meta_invalid) {
+            DMSG1(ctx, DDEBUG, "metaData handling: %d\n", mtype);
+            if (perform_read_metadata_info(ctx, mtype, sp) != bRC_OK) {
+               DMSG0(ctx, DERROR, "Cannot perform_read_metadata_info!\n");
+               JMSG0(ctx, backend.ctx->jmsg_err_level(), "Cannot perform_read_metadata_info!\n");
+               return bRC_Error;
+            }
+            continue;
+         } else {
+            DMSG1(ctx, DERROR, "Invalid File Attributes command: %s\n", cmd.c_str());
+            JMSG1(ctx, backend.ctx->jmsg_err_level(), "Invalid File Attributes command: %s\n", cmd.c_str());
+            return bRC_Error;
          }
       }
 
       DMSG0(ctx, DINFO, "File attributes end.\n");
-      if (reqparams > 0)
-      {
+      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;
@@ -1958,8 +2035,7 @@ bRC METAPLUGIN::startBackupFile(bpContext *ctx, struct save_pkt *sp)
 
    } else {
       // handle Plugin Object parameters
-      if (perform_read_pluginobject(ctx, sp) != bRC_OK)
-      {
+      if (perform_read_pluginobject(ctx, sp) != bRC_OK) {
          // signal error
          return bRC_Error;
       }
@@ -1967,7 +2043,7 @@ bRC METAPLUGIN::startBackupFile(bpContext *ctx, struct save_pkt *sp)
       size = sp->plugin_obj.object_size;
    }
 
-   if (backend.ctx->is_error()){
+   if (backend.ctx->is_error()) {
       return bRC_Error;
    }
 
@@ -2289,6 +2365,38 @@ bRC METAPLUGIN::queryParameter(bpContext *ctx, struct query_pkt *qp)
    return ret;
 }
 
+/**
+ * @brief Sends metadata to backend for restore.
+ *
+ * @param ctx for Bacula debug and jobinfo messages
+ * @param mp
+ * @return bRC
+ */
+bRC METAPLUGIN::metadataRestore(bpContext *ctx, struct meta_pkt *mp)
+{
+   POOL_MEM cmd(PM_FNAME);
+
+   if (mp->buf != NULL && mp->buf_len > 0){
+      /* send command METADATA */
+      // pm_strcpy(cmd, "METADATA_STREAM\n");
+      pm_strcpy(cmd, prepare_metadata_type(mp->type));
+      backend.ctx->write_command(ctx, cmd.c_str());
+      /* send metadata stream data */
+      DMSG1(ctx, DINFO, "writeMetadata: %i\n", mp->buf_len);
+      int rc = backend.ctx->write_data(ctx, (char*)mp->buf, mp->buf_len);
+      if (rc < 0){
+         /* got some error */
+         return bRC_Error;
+      }
+      /* signal end of metadata stream to restore and get ack */
+      if (!backend.ctx->send_ack(ctx)){
+         return bRC_Error;
+      }
+   }
+
+   return bRC_OK;
+}
+
 /*
  * Called here to make a new instance of the plugin -- i.e. when
  * a new Job is started.  There can be multiple instances of
@@ -2497,3 +2605,13 @@ static bRC queryParameter(bpContext *ctx, struct query_pkt *qp)
    METAPLUGIN *self = pluginclass(ctx);
    return self->queryParameter(ctx, qp);
 }
+
+/* Metadata Restore interface */
+static bRC metadataRestore(bpContext *ctx, struct meta_pkt *mp)
+{
+   ASSERT_CTX;
+
+   DMSG2(ctx, D1, "metadataRestore: %d %d\n", mp->total_size, mp->type);
+   METAPLUGIN *self = pluginclass(ctx);
+   return self->metadataRestore(ctx, mp);
+}
index 680c2f953bea43e5f515da4d7ef0ae3c5ee3785c..6b895ca1009e5115824981a325611048019314a0 100644 (file)
@@ -20,8 +20,8 @@
  * @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
+ * @version 2.2.0
+ * @date 2021-03-08
  *
  * @copyright Copyright (c) 2021 All rights reserved. IP transferred to Bacula Systems according to agreement.
  */
@@ -30,6 +30,7 @@
 #include "ptcomm.h"
 #include "lib/ini.h"
 #include "pluginlib/commctx.h"
+#include "pluginlib/smartalist.h"
 
 
 #define USE_CMD_PARSER
@@ -60,6 +61,14 @@ extern struct ini_items plugin_items_dump[];
 // the list of valid plugin options
 extern const char *valid_params[];
 
+struct metadataTypeMap
+{
+   const char *command;
+   metadata_type type;
+};
+
+extern const metadataTypeMap plugin_metadata_map[];
+
 /*
  * 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
@@ -94,6 +103,7 @@ public:
    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 metadataRestore(bpContext *ctx, struct meta_pkt *mp);
    void setup_backend_command(bpContext *ctx, POOL_MEM &exepath);
    METAPLUGIN(bpContext *bpctx);
 #if __cplusplus > 201103L
@@ -148,6 +158,8 @@ private:
    POOL_MEM xattrdata;           // the buffer for XATTR data received from backend
    cmd_parser parser;            // Plugin command parser
    ConfigFile ini;               // Restore ini file handler
+   alist metadatas_list;         //
+   plugin_metadata metadatas;    //
 
    bRC parse_plugin_command(bpContext *ctx, const char *command, alist *params);
    bRC parse_plugin_restoreobj(bpContext *ctx, restore_object_pkt *rop);
@@ -173,6 +185,10 @@ private:
    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);
+   bRC perform_read_metadata_info(bpContext *ctx, metadata_type type, struct save_pkt *sp);
+   // bRC perform_write_metadata_info(bpContext *ctx, struct meta_pkt *mp);
+   metadata_type scan_metadata_type(const POOL_MEM &cmd);
+   const char *prepare_metadata_type(metadata_type type);
    int check_ini_param(char *param);
    bool check_plugin_param(const char *param, alist *params);
    int get_ini_count();
index 178d9e1883e6a46ccdd6ab238fc35d58904fb127..d092c4dfc81ea2e5b61a1def596395bb8a3998ff 100644 (file)
@@ -39,7 +39,7 @@ template <typename T>
 class smart_alist : public alist
 {
 public:
-   smart_alist(int num = 10) : alist(num, not_owned_by_alist) {};
+   smart_alist(int num = 10) : alist(num, not_owned_by_alist) {}
    ~smart_alist()
    {
       T * it;
@@ -52,7 +52,7 @@ public:
             }
          }
       }
-   };
+   }
 };
 
 #endif   /* _SMARTALIST_H_ */
index 7e15c48e59fe5f291436991f5b496e00677f0f03..456eab2ff51d2ec1154d7effbe976087b33bdd9f 100644 (file)
@@ -20,8 +20,8 @@
  * @file test_metaplugin_backend.cpp
  * @author Radosław Korzeniewski (radoslaw@korzeniewski.net)
  * @brief This is a dumb and extremely simple backend simulator used for test Metaplugin.
- * @version 2.1.0
- * @date 2020-12-23
+ * @version 2.1.1
+ * @date 2021-03-10
  *
  * @copyright Copyright (c) 2020 All rights reserved. IP transferred to Bacula Systems according to agreement.
  */
@@ -58,10 +58,17 @@ bool regress_error_estimate_stderr = false;
 bool regress_error_listing_stderr = false;
 bool regress_error_restore_stderr = false;
 bool regress_backup_other_file = false;
+bool regress_metadata_support = false;
+
 
 #define BUFLEN             4096
 #define BIGBUFLEN          65536
 
+/**
+ * @brief saves the log text to logfile
+ *
+ * @param txt log text to save
+ */
 void LOG(const char *txt)
 {
    char _buf[BUFLEN];
@@ -83,6 +90,12 @@ void LOG(const char *txt)
    write(logfd, _buf, p);
 }
 
+/**
+ * @brief Reads the raw packet from plugin.
+ *
+ * @param buf the memory buffer to save packet payload
+ * @return int the size of the packer read
+ */
 int read_plugin(char * buf)
 {
    int len;
@@ -132,6 +145,12 @@ int read_plugin(char * buf)
    return len;
 }
 
+/**
+ * @brief Sends/writes the data to plugin with assembling the raw packet.
+ *
+ * @param cmd the packet type to sent
+ * @param str the text to write
+ */
 void write_plugin(const char cmd, const char *str)
 {
    int len;
@@ -151,18 +170,27 @@ void write_plugin(const char cmd, const char *str)
    LOG(buflog);
 }
 
+/**
+ * @brief Sends the EOD packet to plugin.
+ */
 void signal_eod(){
    printf("F000000\n");
    fflush(stdout);
    LOG("<< EOD <<");
 }
 
+/**
+ * @brief Sends the termination packet to plugin.
+ */
 void signal_term(){
    printf("T000000\n");
    fflush(stdout);
    LOG("<< TERM <<");
 }
 
+/**
+ * @brief Perform test backup.
+ */
 void perform_backup()
 {
    // Backup Loop
@@ -390,6 +418,42 @@ void perform_backup()
    write_plugin('D', "/* here comes another file line    */");
    signal_eod();
 
+   if (regress_metadata_support)
+   {
+      snprintf(buf, BIGBUFLEN, "FNAME:%s/office/%d/document.docx\n", PLUGINPREFIX, mypid);
+      write_plugin('C', buf);
+      write_plugin('C', "STAT:D 10240 100 100 040755 1\n");
+      write_plugin('C', "TSTAMP:1504271937 1504271937 1504271937\n");
+
+      write_plugin('C', "METADATA_STREAM\n");
+         write_plugin('D', "{ \"bacula.custom.data\": \"Inteos\"\n  \"system.custom.data\":\"Bacula\" }\n");
+      signal_eod();
+
+      write_plugin('C', "METADATA_STREAM\n");
+         write_plugin('D', "This is a binary data!");
+      signal_eod();
+
+      // disabled intentionally
+      // write_plugin('C', "METADATA_CATALOG\n");
+      //    write_plugin('D', "TABLE1: { field1: \"value1\", field2: \"value2\", field3: \"value3\"}");
+      // signal_eod();
+
+      // write_plugin('C', "METADATA_CATALOG\n");
+      //    write_plugin('D', "TABLE2: { field2: \"value1\", field2: \"value2\", field3: \"value3\"}");
+      // signal_eod();
+
+      signal_eod();  // end of file attributes
+
+      write_plugin('I', "TEST14 - backup metadata");
+      write_plugin('C', "DATA\n");
+      write_plugin('D', "/* here comes a file data contents */");
+      write_plugin('D', "/* here comes another file line    */");
+      write_plugin('D', "/* here comes another file line    */");
+      write_plugin('D', "/* here comes another file line    */");
+      write_plugin('D', "/* here comes another file line    */");
+      signal_eod();
+   }
+
    /* this is the end of all data */
    signal_eod();
 }
@@ -664,58 +728,51 @@ int main(int argc, char** argv) {
       // "regress_error_restore_stderr",
       // "regress_backup_plugin_objects",
       // "regress_error_backup_abort",
-      if (strcmp(buf, "regress_error_plugin_params=1\n") == 0)
-      {
+      if (strcmp(buf, "regress_error_plugin_params=1\n") == 0){
          regress_error_plugin_params = true;
          continue;
       }
-      if (strcmp(buf, "regress_error_start_job=1\n") == 0)
-      {
+      if (strcmp(buf, "regress_error_start_job=1\n") == 0){
          regress_error_start_job = true;
          continue;
       }
-      if (strcmp(buf, "regress_error_backup_no_files=1\n") == 0)
-      {
+      if (strcmp(buf, "regress_error_backup_no_files=1\n") == 0){
          regress_error_backup_no_files = true;
          continue;
       }
-      if (strcmp(buf, "regress_error_backup_stderr=1\n") == 0)
-      {
+      if (strcmp(buf, "regress_error_backup_stderr=1\n") == 0){
          regress_error_backup_stderr = true;
          continue;
       }
-      if (strcmp(buf, "regress_error_estimate_stderr=1\n") == 0)
-      {
+      if (strcmp(buf, "regress_error_estimate_stderr=1\n") == 0){
          regress_error_estimate_stderr = true;
          continue;
       }
-      if (strcmp(buf, "regress_error_listing_stderr=1\n") == 0)
-      {
+      if (strcmp(buf, "regress_error_listing_stderr=1\n") == 0){
          regress_error_listing_stderr = true;
          continue;
       }
-      if (strcmp(buf, "regress_error_restore_stderr=1\n") == 0)
-      {
+      if (strcmp(buf, "regress_error_restore_stderr=1\n") == 0){
          regress_error_restore_stderr = true;
          continue;
       }
-      if (strcmp(buf, "regress_backup_plugin_objects=1\n") == 0)
-      {
+      if (strcmp(buf, "regress_backup_plugin_objects=1\n") == 0){
          regress_backup_plugin_objects = true;
          continue;
       }
-      if (strcmp(buf, "regress_error_backup_abort=1\n") == 0)
-      {
+      if (strcmp(buf, "regress_error_backup_abort=1\n") == 0){
          regress_error_backup_abort = true;
          continue;
       }
-      if (strcmp(buf, "regress_backup_other_file=1\n") == 0)
-      {
+      if (strcmp(buf, "regress_backup_other_file=1\n") == 0){
          regress_backup_other_file = true;
          continue;
       }
-      if (sscanf(buf, "listing=%s\n", buf) == 1)
-      {
+      if (strcmp(buf, "regress_metadata_support=1\n") == 0){
+         regress_metadata_support = true;
+         continue;
+      }
+      if (sscanf(buf, "listing=%s\n", buf) == 1){
          strcpy(listing, buf);
          continue;
       }