]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/filed/backup.c
Do not try to strip RestoreObject during attribute encoding
[bacula/bacula] / bacula / src / filed / backup.c
index a8303798bfac11859c2462ab78913efa607fb8fa..da9330feff0a1a46ed70596c0921874b637dbba7 100644 (file)
@@ -1,12 +1,12 @@
 /*
    Bacula® - The Network Backup Solution
 
-   Copyright (C) 2000-2010 Free Software Foundation Europe e.V.
+   Copyright (C) 2000-2011 Free Software Foundation Europe e.V.
 
    The main author of Bacula is Kern Sibbald, with contributions from
    many others, a complete list can be found in the file AUTHORS.
    This program is Free Software; you can redistribute it and/or
-   modify it under the terms of version two of the GNU General Public
+   modify it under the terms of version three of the GNU Affero General Public
    License as published by the Free Software Foundation and included
    in the file LICENSE.
 
@@ -15,7 +15,7 @@
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    General Public License for more details.
 
-   You should have received a copy of the GNU General Public License
+   You should have received a copy of the GNU Affero General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
@@ -35,6 +35,7 @@
 
 #include "bacula.h"
 #include "filed.h"
+#include "ch.h"
 
 #ifdef HAVE_DARWIN_OS
 const bool have_darwin_os = true;
@@ -61,6 +62,7 @@ bool encode_and_send_attributes(JCR *jcr, FF_PKT *ff_pkt, int &data_stream);
 static bool crypto_session_start(JCR *jcr);
 static void crypto_session_end(JCR *jcr);
 static bool crypto_session_send(JCR *jcr, BSOCK *sd);
+static void close_vss_backup_session(JCR *jcr);
 
 /**
  * Find all the requested files and send them
@@ -82,7 +84,7 @@ bool blast_data_to_storage_daemon(JCR *jcr, char *addr)
 
    sd = jcr->store_bsock;
 
-   set_jcr_job_status(jcr, JS_Running);
+   jcr->setJobStatus(JS_Running);
 
    Dmsg1(300, "bfiled: opened data connection %d to stored\n", sd->m_fd);
 
@@ -96,7 +98,7 @@ bool blast_data_to_storage_daemon(JCR *jcr, char *addr)
       buf_size = 0;                   /* use default */
    }
    if (!sd->set_buffer_size(buf_size, BNET_SETBUF_WRITE)) {
-      set_jcr_job_status(jcr, JS_ErrorTerminated);
+      jcr->setJobStatus(JS_ErrorTerminated);
       Jmsg(jcr, M_FATAL, 0, _("Cannot set buffer size FD->SD.\n"));
       return false;
    }
@@ -109,12 +111,22 @@ bool blast_data_to_storage_daemon(JCR *jcr, char *addr)
     *  Note, we adjust the read size to be smaller so that the
     *  same output buffer can be used without growing it.
     *
+    *  For LZO1X compression the recommended value is :
+    *                  output_block_size = input_block_size + (input_block_size / 16) + 64 + 3 + sizeof(comp_stream_header)
+    *
     * The zlib compression workset is initialized here to minimize
     *  the "per file" load. The jcr member is only set, if the init 
     *  was successful.
+    *
+    *  For the same reason, lzo compression is initialized here.
     */
+#ifdef HAVE_LZO
+   jcr->compress_buf_size = MAX(jcr->buf_size + (jcr->buf_size / 16) + 67 + (int)sizeof(comp_stream_header), jcr->buf_size + ((jcr->buf_size+999) / 1000) + 30);
+   jcr->compress_buf = get_memory(jcr->compress_buf_size);
+#else
    jcr->compress_buf_size = jcr->buf_size + ((jcr->buf_size+999) / 1000) + 30;
    jcr->compress_buf = get_memory(jcr->compress_buf_size);
+#endif
    
 #ifdef HAVE_LIBZ
    z_stream *pZlibStream = (z_stream*)malloc(sizeof(z_stream));  
@@ -132,13 +144,24 @@ bool blast_data_to_storage_daemon(JCR *jcr, char *addr)
    }
 #endif
 
+#ifdef HAVE_LZO
+   lzo_voidp pLzoMem = (lzo_voidp) malloc(LZO1X_1_MEM_COMPRESS);
+   if (pLzoMem) {
+      if (lzo_init() == LZO_E_OK) {
+         jcr->LZO_compress_workset = pLzoMem;
+      } else {
+         free (pLzoMem);
+      }
+   }
+#endif
+
    if (!crypto_session_start(jcr)) {
       return false;
    }
 
    set_find_options((FF_PKT *)jcr->ff, jcr->incremental, jcr->mtime);
 
-   /** in accurate mode, we overwrite the find_one check function */
+   /** in accurate mode, we overload the find_one check function */
    if (jcr->accurate) {
       set_find_changed_function((FF_PKT *)jcr->ff, accurate_check_file);
    } 
@@ -147,31 +170,37 @@ bool blast_data_to_storage_daemon(JCR *jcr, char *addr)
 
    if (have_acl) {
       jcr->acl_data = (acl_data_t *)malloc(sizeof(acl_data_t));
-      memset((caddr_t)jcr->acl_data, 0, sizeof(acl_data_t));
-      jcr->acl_data->content = get_pool_memory(PM_MESSAGE);
+      memset(jcr->acl_data, 0, sizeof(acl_data_t));
+      jcr->acl_data->u.build = (acl_build_data_t *)malloc(sizeof(acl_build_data_t));
+      memset(jcr->acl_data->u.build, 0, sizeof(acl_build_data_t));
+      jcr->acl_data->u.build->content = get_pool_memory(PM_MESSAGE);
    }
 
    if (have_xattr) {
       jcr->xattr_data = (xattr_data_t *)malloc(sizeof(xattr_data_t));
-      memset((caddr_t)jcr->xattr_data, 0, sizeof(xattr_data_t));
-      jcr->xattr_data->content = get_pool_memory(PM_MESSAGE);
+      memset(jcr->xattr_data, 0, sizeof(xattr_data_t));
+      jcr->xattr_data->u.build = (xattr_build_data_t *)malloc(sizeof(xattr_build_data_t));
+      memset(jcr->xattr_data->u.build, 0, sizeof(xattr_build_data_t));
+      jcr->xattr_data->u.build->content = get_pool_memory(PM_MESSAGE);
    }
 
    /** Subroutine save_file() is called for each file */
    if (!find_files(jcr, (FF_PKT *)jcr->ff, save_file, plugin_save)) {
       ok = false;                     /* error */
-      set_jcr_job_status(jcr, JS_ErrorTerminated);
+      jcr->setJobStatus(JS_ErrorTerminated);
    }
 
-   if (have_acl && jcr->acl_data->nr_errors > 0) {
-      Jmsg(jcr, M_ERROR, 0, _("Encountered %ld acl errors while doing backup\n"),
-           jcr->acl_data->nr_errors);
+   if (have_acl && jcr->acl_data->u.build->nr_errors > 0) {
+      Jmsg(jcr, M_WARNING, 0, _("Encountered %ld acl errors while doing backup\n"),
+           jcr->acl_data->u.build->nr_errors);
    }
-   if (have_xattr && jcr->xattr_data->nr_errors > 0) {
-      Jmsg(jcr, M_ERROR, 0, _("Encountered %ld xattr errors while doing backup\n"),
-           jcr->xattr_data->nr_errors);
+   if (have_xattr && jcr->xattr_data->u.build->nr_errors > 0) {
+      Jmsg(jcr, M_WARNING, 0, _("Encountered %ld xattr errors while doing backup\n"),
+           jcr->xattr_data->u.build->nr_errors);
    }
 
+   close_vss_backup_session(jcr);
+
    accurate_finish(jcr);              /* send deleted or base file list to SD */
 
    stop_heartbeat_monitor(jcr);
@@ -179,12 +208,14 @@ bool blast_data_to_storage_daemon(JCR *jcr, char *addr)
    sd->signal(BNET_EOD);            /* end of sending data */
 
    if (have_acl && jcr->acl_data) {
-      free_pool_memory(jcr->acl_data->content);
+      free_pool_memory(jcr->acl_data->u.build->content);
+      free(jcr->acl_data->u.build);
       free(jcr->acl_data);
       jcr->acl_data = NULL;
    }
    if (have_xattr && jcr->xattr_data) {
-      free_pool_memory(jcr->xattr_data->content);
+      free_pool_memory(jcr->xattr_data->u.build->content);
+      free(jcr->xattr_data->u.build);
       free(jcr->xattr_data);
       jcr->xattr_data = NULL;
    }
@@ -204,6 +235,11 @@ bool blast_data_to_storage_daemon(JCR *jcr, char *addr)
       free (jcr->pZLIB_compress_workset);
       jcr->pZLIB_compress_workset = NULL;
    }
+   if (jcr->LZO_compress_workset) {
+      free (jcr->LZO_compress_workset);
+      jcr->LZO_compress_workset = NULL;
+   }
+
    crypto_session_end(jcr);
 
 
@@ -299,6 +335,8 @@ static bool crypto_session_send(JCR *jcr, BSOCK *sd)
 int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
 {
    bool do_read = false;
+   bool plugin_started = false;
+   bool do_plugin_set = false;
    int stat, data_stream; 
    int rtnstat = 0;
    DIGEST *digest = NULL;
@@ -306,6 +344,7 @@ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
    int digest_stream = STREAM_NONE;
    SIGNATURE *sig = NULL;
    bool has_file_data = false;
+   struct save_pkt sp;          /* use by option plugin */
    // TODO landonf: Allow the user to specify the digest algorithm
 #ifdef HAVE_SHA2
    crypto_digest_t signing_algorithm = CRYPTO_DIGEST_SHA256;
@@ -314,7 +353,7 @@ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
 #endif
    BSOCK *sd = jcr->store_bsock;
 
-   if (jcr->is_job_canceled()) {
+   if (jcr->is_canceled() || jcr->is_incomplete()) {
       return 0;
    }
 
@@ -336,7 +375,10 @@ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
       Dmsg2(130, "FT_LNK saving: %s -> %s\n", ff_pkt->fname, ff_pkt->link);
       break;
    case FT_RESTORE_FIRST:
-      Dmsg1(00, "FT_RESTORE_FIRST saving: %s\n", ff_pkt->fname);
+      Dmsg1(100, "FT_RESTORE_FIRST saving: %s\n", ff_pkt->fname);
+      break;
+   case FT_PLUGIN_CONFIG:
+      Dmsg1(100, "FT_PLUGIN_CONFIG saving: %s\n", ff_pkt->fname);
       break;
    case FT_DIRBEGIN:
       jcr->num_files_examined--;      /* correct file count */
@@ -349,8 +391,8 @@ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
    case FT_NOFSCHG:
       /* Suppress message for /dev filesystems */
       if (!is_in_fileset(ff_pkt)) {
-         Jmsg(jcr, M_INFO, 1, _("     %s is a different filesystem. Will not descend from %s into %s\n"),
-              ff_pkt->fname, ff_pkt->top_fname, ff_pkt->fname);
+         Jmsg(jcr, M_INFO, 1, _("     %s is a different filesystem. Will not descend from %s into it.\n"),
+              ff_pkt->fname, ff_pkt->top_fname);
       }
       ff_pkt->type = FT_DIREND;       /* Backup only the directory entry */
       break;
@@ -364,6 +406,7 @@ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
            ff_pkt->fname);
       break;
    case FT_REPARSE:
+   case FT_JUNCTION:
    case FT_DIREND:
       Dmsg1(130, "FT_DIREND: %s\n", ff_pkt->link);
       break;
@@ -416,6 +459,9 @@ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
       jcr->JobErrors++;
       return 1;
    }
+   case FT_DELETED:
+      Dmsg1(130, "FT_DELETED: %s\n", ff_pkt->fname);
+      break;
    default:
       Jmsg(jcr, M_NOTSAVED, 0,  _("     Unknown file type %d; not saved: %s\n"), 
            ff_pkt->type, ff_pkt->fname);
@@ -491,19 +537,52 @@ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
    if (ff_pkt->flags & FO_PORTABLE) {
       set_portable_backup(&ff_pkt->bfd); /* disable Win32 BackupRead() */
    }
+
    if (ff_pkt->cmd_plugin) {
+      do_plugin_set = true;
+
+   /* option and cmd plugin are not compatible together */
+   } else if (ff_pkt->opt_plugin) {
+      
+      /* ask the option plugin what to do with this file */
+      switch (plugin_option_handle_file(jcr, ff_pkt, &sp)) {
+      case bRC_OK:
+         Dmsg2(10, "Option plugin %s will be used to backup %s\n",
+               ff_pkt->plugin, ff_pkt->fname);
+         do_plugin_set = true;
+         break;
+      case bRC_Skip:
+         Dmsg2(10, "Option plugin %s decided to skip %s\n",
+               ff_pkt->plugin, ff_pkt->fname);
+         goto good_rtn;
+      default:
+         Dmsg2(10, "Option plugin %s decided to let bacula handle %s\n",
+               ff_pkt->plugin, ff_pkt->fname);
+         break;
+      }
+   }
+
+   if (do_plugin_set) {
       /* Tell bfile that it needs to call plugin */
       if (!set_cmd_plugin(&ff_pkt->bfd, jcr)) {
          goto bail_out;
       }
       send_plugin_name(jcr, sd, true);      /* signal start of plugin data */
+      plugin_started = true;
    }
 
    /** Send attributes -- must be done after binit() */
    if (!encode_and_send_attributes(jcr, ff_pkt, data_stream)) {
       goto bail_out;
    }
-
+   /** Meta data only for restore object */
+   if (IS_FT_OBJECT(ff_pkt->type)) {
+      goto good_rtn;
+   }
+   /** Meta data only for deleted files */
+   if (ff_pkt->type == FT_DELETED) {
+      goto good_rtn;
+   }
    /** Set up the encryption context and send the session data to the SD */
    if (has_file_data && jcr->crypto.pki_encrypt) {
       if (!crypto_session_send(jcr, sd)) {
@@ -524,15 +603,16 @@ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
       do_read = ff_pkt->statp.st_size > 0;  
 #endif
    } else if (ff_pkt->type == FT_RAW || ff_pkt->type == FT_FIFO ||
-              ff_pkt->type == FT_REPARSE ||
+              ff_pkt->type == FT_REPARSE || ff_pkt->type == FT_JUNCTION ||
          (!is_portable_backup(&ff_pkt->bfd) && ff_pkt->type == FT_DIREND)) {
       do_read = true;
    }
-   if (ff_pkt->cmd_plugin) {
+
+   if (ff_pkt->cmd_plugin && !ff_pkt->no_read) {
       do_read = true;
    }
 
-   Dmsg1(400, "do_read=%d\n", do_read);
+   Dmsg2(150, "type=%d do_read=%d\n", ff_pkt->type, do_read);
    if (do_read) {
       btimer_t *tid;
 
@@ -542,7 +622,8 @@ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
          tid = NULL;
       }
       int noatime = ff_pkt->flags & FO_NOATIME ? O_NOATIME : 0;
-      ff_pkt->bfd.reparse_point = ff_pkt->type == FT_REPARSE;
+      ff_pkt->bfd.reparse_point = (ff_pkt->type == FT_REPARSE || 
+                                   ff_pkt->type == FT_JUNCTION);
       if (bopen(&ff_pkt->bfd, ff_pkt->fname, O_RDONLY | O_BINARY | noatime, 0) < 0) {
          ff_pkt->ff_errno = errno;
          berrno be;
@@ -592,7 +673,7 @@ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
                goto good_rtn;
             }
             flags = ff_pkt->flags;
-            ff_pkt->flags &= ~(FO_GZIP|FO_SPARSE);
+            ff_pkt->flags &= ~(FO_COMPRESS|FO_SPARSE|FO_OFFSETS);
             if (flags & FO_ENCRYPT) {
                rsrc_stream = STREAM_ENCRYPTED_MACOS_FORK_DATA;
             } else {
@@ -623,7 +704,8 @@ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
    }
 
    /**
-    * Save ACLs when requested and available for anything not being a symlink and not being a plugin.
+    * Save ACLs when requested and available for anything not being a symlink
+    * and not being a plugin.
     */
    if (have_acl) {
       if (ff_pkt->flags & FO_ACL && ff_pkt->type != FT_LNK && !ff_pkt->cmd_plugin) {
@@ -632,13 +714,14 @@ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
             goto bail_out;
          case bacl_exit_error:
             /**
-             * Non-fatal errors, count them and when the number is under ACL_REPORT_ERR_MAX_PER_JOB
-             * print the error message set by the lower level routine in jcr->errmsg.
+             * Non-fatal errors, count them and when the number is under
+             * ACL_REPORT_ERR_MAX_PER_JOB print the error message set by the
+             * lower level routine in jcr->errmsg.
              */
-            if (jcr->acl_data->nr_errors < ACL_REPORT_ERR_MAX_PER_JOB) {
-               Jmsg(jcr, M_ERROR, 0, "%s", jcr->errmsg);
+            if (jcr->acl_data->u.build->nr_errors < ACL_REPORT_ERR_MAX_PER_JOB) {
+               Jmsg(jcr, M_WARNING, 0, "%s", jcr->errmsg);
             }
-            jcr->acl_data->nr_errors++;
+            jcr->acl_data->u.build->nr_errors++;
             break;
          case bacl_exit_ok:
             break;
@@ -647,7 +730,8 @@ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
    }
 
    /**
-    * Save Extended Attributes when requested and available for all files not being a plugin.
+    * Save Extended Attributes when requested and available for all files not
+    * being a plugin.
     */
    if (have_xattr) {
       if (ff_pkt->flags & FO_XATTR && !ff_pkt->cmd_plugin) {
@@ -656,13 +740,14 @@ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
             goto bail_out;
          case bxattr_exit_error:
             /**
-             * Non-fatal errors, count them and when the number is under XATTR_REPORT_ERR_MAX_PER_JOB
-             * print the error message set by the lower level routine in jcr->errmsg.
+             * Non-fatal errors, count them and when the number is under
+             * XATTR_REPORT_ERR_MAX_PER_JOB print the error message set by the
+             * lower level routine in jcr->errmsg.
              */
-            if (jcr->xattr_data->nr_errors < XATTR_REPORT_ERR_MAX_PER_JOB) {
-               Jmsg(jcr, M_ERROR, 0, "%s", jcr->errmsg);
+            if (jcr->xattr_data->u.build->nr_errors < XATTR_REPORT_ERR_MAX_PER_JOB) {
+               Jmsg(jcr, M_WARNING, 0, "%s", jcr->errmsg);
             }
-            jcr->xattr_data->nr_errors++;
+            jcr->xattr_data->u.build->nr_errors++;
             break;
          case bxattr_exit_ok:
             break;
@@ -729,18 +814,45 @@ int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
          goto bail_out;
       }
 
+      /* Keep the checksum if this file is a hardlink */
+      if (ff_pkt->linked) {
+         ff_pkt_set_link_digest(ff_pkt, digest_stream, sd->msg, size);
+      }
+
       sd->msglen = size;
       sd->send();
       sd->signal(BNET_EOD);              /* end of checksum */
    }
-   if (ff_pkt->cmd_plugin) {
-      send_plugin_name(jcr, sd, false); /* signal end of plugin data */
+
+   /* Check if original file has a digest, and send it */
+   if (ff_pkt->type == FT_LNKSAVED && ff_pkt->digest) {
+      Dmsg2(300, "Link %s digest %d\n", ff_pkt->fname, ff_pkt->digest_len);
+      sd->fsend("%ld %d 0", jcr->JobFiles, ff_pkt->digest_stream);
+
+      sd->msg = check_pool_memory_size(sd->msg, ff_pkt->digest_len);
+      memcpy(sd->msg, ff_pkt->digest, ff_pkt->digest_len);
+      sd->msglen = ff_pkt->digest_len;
+      sd->send();
+
+      sd->signal(BNET_EOD);              /* end of hardlink record */
    }
 
 good_rtn:
-   rtnstat = 1;                       /* good return */
+   rtnstat = jcr->is_canceled() ? 0 : 1; /* good return if not canceled */
 
 bail_out:
+   if (jcr->is_incomplete() || jcr->is_canceled()) {
+      rtnstat = 0;
+   }
+   if (plugin_started) {
+      send_plugin_name(jcr, sd, false); /* signal end of plugin data */
+   }
+   if (ff_pkt->opt_plugin) {
+      jcr->plugin_sp = NULL;    /* sp is local to this function */
+      jcr->plugin_ctx = NULL;
+      jcr->plugin = NULL;
+      jcr->opt_plugin = false;
+   }
    if (digest) {
       crypto_digest_free(digest);
    }
@@ -788,16 +900,17 @@ static int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest,
 
    Dmsg1(300, "Saving data, type=%d\n", ff_pkt->type);
 
-#ifdef HAVE_LIBZ
+#if defined(HAVE_LIBZ) || defined(HAVE_LZO)
    uLong compress_len = 0;
    uLong max_compress_len = 0;
    const Bytef *cbuf = NULL;
+ #ifdef HAVE_LIBZ
    int zstat;
 
-   if (ff_pkt->flags & FO_GZIP) {
-      if (ff_pkt->flags & FO_SPARSE) {
-         cbuf = (Bytef *)jcr->compress_buf + SPARSE_FADDR_SIZE;
-         max_compress_len = jcr->compress_buf_size - SPARSE_FADDR_SIZE;
+   if ((ff_pkt->flags & FO_COMPRESS) && ff_pkt->Compress_algo == COMPRESS_GZIP) {
+      if ((ff_pkt->flags & FO_SPARSE) || (ff_pkt->flags & FO_OFFSETS)) {
+         cbuf = (Bytef *)jcr->compress_buf + OFFSET_FADDR_SIZE;
+         max_compress_len = jcr->compress_buf_size - OFFSET_FADDR_SIZE;
       } else {
          cbuf = (Bytef *)jcr->compress_buf;
          max_compress_len = jcr->compress_buf_size; /* set max length */
@@ -814,20 +927,45 @@ static int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest,
       if (((z_stream*)jcr->pZLIB_compress_workset)->total_in == 0) {
          /** set gzip compression level - must be done per file */
          if ((zstat=deflateParams((z_stream*)jcr->pZLIB_compress_workset, 
-              ff_pkt->GZIP_level, Z_DEFAULT_STRATEGY)) != Z_OK) {
+              ff_pkt->Compress_level, Z_DEFAULT_STRATEGY)) != Z_OK) {
             Jmsg(jcr, M_FATAL, 0, _("Compression deflateParams error: %d\n"), zstat);
-            set_jcr_job_status(jcr, JS_ErrorTerminated);
+            jcr->setJobStatus(JS_ErrorTerminated);
             goto err;
          }
       }
    }
+ #endif
+ #ifdef HAVE_LZO
+   Bytef *cbuf2;
+   int lzores;
+   comp_stream_header ch;
+
+   memset(&ch, 0, sizeof(comp_stream_header));
+   cbuf2 = NULL;
+
+   if ((ff_pkt->flags & FO_COMPRESS) && ff_pkt->Compress_algo == COMPRESS_LZO1X) {
+      if ((ff_pkt->flags & FO_SPARSE) || (ff_pkt->flags & FO_OFFSETS)) {
+         cbuf = (Bytef *)jcr->compress_buf + OFFSET_FADDR_SIZE;
+         cbuf2 = (Bytef *)jcr->compress_buf + OFFSET_FADDR_SIZE + sizeof(comp_stream_header);
+         max_compress_len = jcr->compress_buf_size - OFFSET_FADDR_SIZE;
+      } else {
+         cbuf = (Bytef *)jcr->compress_buf;
+         cbuf2 = (Bytef *)jcr->compress_buf + sizeof(comp_stream_header);
+         max_compress_len = jcr->compress_buf_size; /* set max length */
+      }
+      ch.magic = COMPRESS_LZO1X;
+      ch.version = COMP_HEAD_VERSION;
+      wbuf = jcr->compress_buf;    /* compressed output here */
+      cipher_input = (uint8_t *)jcr->compress_buf; /* encrypt compressed data */
+   }
+ #endif
 #else
    const uint32_t max_compress_len = 0;
 #endif
 
    if (ff_pkt->flags & FO_ENCRYPT) {
-      if (ff_pkt->flags & FO_SPARSE) {
-         Jmsg0(jcr, M_FATAL, 0, _("Encrypting sparse data not supported.\n"));
+      if ((ff_pkt->flags & FO_SPARSE) || (ff_pkt->flags & FO_OFFSETS)) {
+         Jmsg0(jcr, M_FATAL, 0, _("Encrypting sparse or offset data not supported.\n"));
          goto err;
       }
       /** Allocate the cipher context */
@@ -869,9 +1007,9 @@ static int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest,
     * Make space at beginning of buffer for fileAddr because this
     *   same buffer will be used for writing if compression is off.
     */
-   if (ff_pkt->flags & FO_SPARSE) {
-      rbuf += SPARSE_FADDR_SIZE;
-      rsize -= SPARSE_FADDR_SIZE;
+   if ((ff_pkt->flags & FO_SPARSE) || (ff_pkt->flags & FO_OFFSETS)) {
+      rbuf += OFFSET_FADDR_SIZE;
+      rsize -= OFFSET_FADDR_SIZE;
 #ifdef HAVE_FREEBSD_OS
       /**
        * To read FreeBSD partitions, the read size must be
@@ -904,7 +1042,7 @@ static int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest,
          }
          if (!allZeros) {
             /** Put file address as first data in buffer */
-            ser_begin(wbuf, SPARSE_FADDR_SIZE);
+            ser_begin(wbuf, OFFSET_FADDR_SIZE);
             ser_uint64(fileAddr);     /* store fileAddr in begin of buffer */
          }
          fileAddr += sd->msglen;      /* update file address */
@@ -912,6 +1050,10 @@ static int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest,
          if (allZeros) {
             continue;                 /* skip block of zeros */
          }
+      } else if (ff_pkt->flags & FO_OFFSETS) {
+         ser_declare;
+         ser_begin(wbuf, OFFSET_FADDR_SIZE);
+         ser_uint64(ff_pkt->bfd.offset);     /* store offset in begin of buffer */
       }
 
       jcr->ReadBytes += sd->msglen;         /* count bytes read */
@@ -931,7 +1073,7 @@ static int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest,
 
 #ifdef HAVE_LIBZ
       /** Do compression if turned on */
-      if (ff_pkt->flags & FO_GZIP && jcr->pZLIB_compress_workset) {
+      if (ff_pkt->flags & FO_COMPRESS && ff_pkt->Compress_algo == COMPRESS_GZIP && jcr->pZLIB_compress_workset) {
          Dmsg3(400, "cbuf=0x%x rbuf=0x%x len=%u\n", cbuf, rbuf, sd->msglen);
          
          ((z_stream*)jcr->pZLIB_compress_workset)->next_in   = (Bytef *)rbuf;
@@ -941,24 +1083,60 @@ static int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest,
 
          if ((zstat=deflate((z_stream*)jcr->pZLIB_compress_workset, Z_FINISH)) != Z_STREAM_END) {
             Jmsg(jcr, M_FATAL, 0, _("Compression deflate error: %d\n"), zstat);
-            set_jcr_job_status(jcr, JS_ErrorTerminated);
+            jcr->setJobStatus(JS_ErrorTerminated);
             goto err;
          }
          compress_len = ((z_stream*)jcr->pZLIB_compress_workset)->total_out;
          /** reset zlib stream to be able to begin from scratch again */
          if ((zstat=deflateReset((z_stream*)jcr->pZLIB_compress_workset)) != Z_OK) {
             Jmsg(jcr, M_FATAL, 0, _("Compression deflateReset error: %d\n"), zstat);
-            set_jcr_job_status(jcr, JS_ErrorTerminated);
+            jcr->setJobStatus(JS_ErrorTerminated);
+            goto err;
+         }
+
+         Dmsg2(400, "GZIP compressed len=%d uncompressed len=%d\n", compress_len, 
+               sd->msglen);
+
+         sd->msglen = compress_len;      /* set compressed length */
+         cipher_input_len = compress_len;
+      }
+#endif
+#ifdef HAVE_LZO
+      /** Do compression if turned on */
+      if (ff_pkt->flags & FO_COMPRESS && ff_pkt->Compress_algo == COMPRESS_LZO1X && jcr->LZO_compress_workset) {
+         lzo_uint len;          /* TODO: See with the latest patch how to handle lzo_uint with 64bit */
+
+         ser_declare;
+         ser_begin(cbuf, sizeof(comp_stream_header));
+
+         Dmsg3(400, "cbuf=0x%x rbuf=0x%x len=%u\n", cbuf, rbuf, sd->msglen);
+
+         lzores = lzo1x_1_compress((const unsigned char*)rbuf, sd->msglen, cbuf2, 
+                                   &len, jcr->LZO_compress_workset);
+         compress_len = len;
+         if (lzores == LZO_E_OK && compress_len <= max_compress_len)
+         {
+            /* complete header */
+            ser_uint32(COMPRESS_LZO1X);
+            ser_uint32(compress_len);
+            ser_uint16(ch.level);
+            ser_uint16(ch.version);
+         } else {
+            /** this should NEVER happen */
+            Jmsg(jcr, M_FATAL, 0, _("Compression LZO error: %d\n"), lzores);
+            jcr->setJobStatus(JS_ErrorTerminated);
             goto err;
          }
 
-         Dmsg2(400, "compressed len=%d uncompressed len=%d\n", compress_len, 
+         Dmsg2(400, "LZO compressed len=%d uncompressed len=%d\n", compress_len, 
                sd->msglen);
 
+         compress_len += sizeof(comp_stream_header); /* add size of header */
          sd->msglen = compress_len;      /* set compressed length */
          cipher_input_len = compress_len;
       }
 #endif
+
       /**
        * Note, here we prepend the current record length to the beginning
        *  of the encrypted data. This is because both sparse and compression
@@ -976,8 +1154,8 @@ static int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest,
          uint32_t initial_len = 0;
          ser_declare;
 
-         if (ff_pkt->flags & FO_SPARSE) {
-            cipher_input_len += SPARSE_FADDR_SIZE;
+         if ((ff_pkt->flags & FO_SPARSE) || (ff_pkt->flags & FO_OFFSETS)) {
+            cipher_input_len += OFFSET_FADDR_SIZE;
          }
 
          /** Encrypt the length of the input block */
@@ -1012,8 +1190,8 @@ static int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest,
       }
 
       /* Send the buffer to the Storage daemon */
-      if (ff_pkt->flags & FO_SPARSE) {
-         sd->msglen += SPARSE_FADDR_SIZE; /* include fileAddr in size */
+      if ((ff_pkt->flags & FO_SPARSE) || (ff_pkt->flags & FO_OFFSETS)) {
+         sd->msglen += OFFSET_FADDR_SIZE; /* include fileAddr in size */
       }
       sd->msg = wbuf;              /* set correct write buffer */
       if (!sd->send()) {
@@ -1096,9 +1274,11 @@ bool encode_and_send_attributes(JCR *jcr, FF_PKT *ff_pkt, int &data_stream)
    BSOCK *sd = jcr->store_bsock;
    char attribs[MAXSTRING];
    char attribsExBuf[MAXSTRING];
-   char *attribsEx;
+   char *attribsEx = NULL;
    int attr_stream;
+   int comp_len;
    bool stat;
+   int hangup = get_hangup();
 #ifdef FD_NO_SEND_TEST
    return true;
 #endif
@@ -1110,11 +1290,15 @@ bool encode_and_send_attributes(JCR *jcr, FF_PKT *ff_pkt, int &data_stream)
       Jmsg0(jcr, M_FATAL, 0, _("Invalid file flags, no supported data stream type.\n"));
       return false;
    }
-   encode_stat(attribs, &ff_pkt->statp, ff_pkt->LinkFI, data_stream);
+   encode_stat(attribs, &ff_pkt->statp, sizeof(ff_pkt->statp), ff_pkt->LinkFI, data_stream);
 
    /** Now possibly extend the attributes */
-   attribsEx = attribsExBuf;
-   attr_stream = encode_attribsEx(jcr, attribsEx, ff_pkt);
+   if (IS_FT_OBJECT(ff_pkt->type)) {
+      attr_stream = STREAM_RESTORE_OBJECT;
+   } else {
+      attribsEx = attribsExBuf;
+      attr_stream = encode_attribsEx(jcr, attribsEx, ff_pkt);
+   }
 
    Dmsg3(300, "File %s\nattribs=%s\nattribsEx=%s\n", ff_pkt->fname, attribs, attribsEx);
 
@@ -1124,12 +1308,20 @@ bool encode_and_send_attributes(JCR *jcr, FF_PKT *ff_pkt, int &data_stream)
    pm_strcpy(jcr->last_fname, ff_pkt->fname);
    jcr->unlock();
 
+   /* Debug code: check if we must hangup */
+   if (hangup && (jcr->JobFiles > (uint32_t)hangup)) {
+      jcr->setJobStatus(JS_Incomplete);
+      Jmsg1(jcr, M_FATAL, 0, "Debug hangup requested after %d files.\n", hangup);
+      set_hangup(0);
+      return false;
+   }
+
    /**
     * Send Attributes header to Storage daemon
     *    <file-index> <stream> <info>
     */
    if (!sd->fsend("%ld %d 0", jcr->JobFiles, attr_stream)) {
-      if (!jcr->is_job_canceled()) {
+      if (!jcr->is_canceled() && !jcr->is_incomplete()) {
          Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
                sd->bstrerror());
       }
@@ -1146,35 +1338,84 @@ bool encode_and_send_attributes(JCR *jcr, FF_PKT *ff_pkt, int &data_stream)
     *   Link name (if type==FT_LNK or FT_LNKSAVED)
     *   Encoded extended-attributes (for Win32)
     *
+    * or send Restore Object to Storage daemon
+    *   File_index
+    *   File_type
+    *   Object_index
+    *   Object_len  (possibly compressed)
+    *   Object_full_len (not compressed)
+    *   Object_compression
+    *   Plugin_name
+    *   Object_name
+    *   Binary Object data
+    *
     * For a directory, link is the same as fname, but with trailing
     * slash. For a linked file, link is the link.
     */
-   if (ff_pkt->type != FT_DELETED) { /* already stripped */
+   if (!IS_FT_OBJECT(ff_pkt->type) && ff_pkt->type != FT_DELETED) { /* already stripped */
       strip_path(ff_pkt);
    }
    switch (ff_pkt->type) {
    case FT_LNK:
    case FT_LNKSAVED:
-      Dmsg2(300, "Link %s to %s\n", ff_pkt->fname, ff_pkt->link);
-      stat = sd->fsend("%ld %d %s%c%s%c%s%c%s%c", jcr->JobFiles,
-               ff_pkt->type, ff_pkt->fname, 0, attribs, 0, ff_pkt->link, 0,
-               attribsEx, 0);
+      Dmsg3(300, "Link %d %s to %s\n", jcr->JobFiles, ff_pkt->fname, ff_pkt->link);
+      stat = sd->fsend("%ld %d %s%c%s%c%s%c%s%c%u%c", jcr->JobFiles,
+                       ff_pkt->type, ff_pkt->fname, 0, attribs, 0, 
+                       ff_pkt->link, 0, attribsEx, 0, ff_pkt->delta_seq, 0);
       break;
    case FT_DIREND:
    case FT_REPARSE:
+   case FT_JUNCTION:
       /* Here link is the canonical filename (i.e. with trailing slash) */
-      stat = sd->fsend("%ld %d %s%c%s%c%c%s%c", jcr->JobFiles,
-               ff_pkt->type, ff_pkt->link, 0, attribs, 0, 0, attribsEx, 0);
+      stat = sd->fsend("%ld %d %s%c%s%c%c%s%c%u%c", jcr->JobFiles,
+                       ff_pkt->type, ff_pkt->link, 0, attribs, 0, 0, 
+                       attribsEx, 0, ff_pkt->delta_seq, 0);
       break;
+   case FT_PLUGIN_CONFIG:
    case FT_RESTORE_FIRST:
-      attribsEx = ff_pkt->object;        /* put object as extended attributes */
-      /* Fall through wanted */
+      comp_len = ff_pkt->object_len;
+      ff_pkt->object_compression = 0;
+      if (ff_pkt->object_len > 1000) {
+         /* Big object, compress it */
+         comp_len = ff_pkt->object_len + 1000;
+         POOLMEM *comp_obj = get_memory(comp_len);
+         /* *** FIXME *** check Zdeflate error */
+         Zdeflate(ff_pkt->object, ff_pkt->object_len, comp_obj, comp_len);
+         if (comp_len < ff_pkt->object_len) {
+            ff_pkt->object = comp_obj;
+            ff_pkt->object_compression = 1;    /* zlib level 9 compression */
+         } else {
+            /* Uncompressed object smaller, use it */
+            comp_len = ff_pkt->object_len;
+         }
+         Dmsg2(100, "Object compressed from %d to %d bytes\n", ff_pkt->object_len, comp_len);
+      }
+      sd->msglen = Mmsg(sd->msg, "%d %d %d %d %d %d %s%c%s%c",
+                        jcr->JobFiles, ff_pkt->type, ff_pkt->object_index,
+                        comp_len, ff_pkt->object_len, ff_pkt->object_compression,
+                        ff_pkt->fname, 0, ff_pkt->object_name, 0);
+      sd->msg = check_pool_memory_size(sd->msg, sd->msglen + comp_len + 2);
+      memcpy(sd->msg + sd->msglen, ff_pkt->object, comp_len);
+      /* Note we send one extra byte so Dir can store zero after object */
+      sd->msglen += comp_len + 1;
+      stat = sd->send();
+      if (ff_pkt->object_compression) {
+         free_and_null_pool_memory(ff_pkt->object);
+      }
+      break;
+   case FT_REG:
+      stat = sd->fsend("%ld %d %s%c%s%c%c%s%c%d%c", jcr->JobFiles,
+               ff_pkt->type, ff_pkt->fname, 0, attribs, 0, 0, attribsEx, 0,
+               ff_pkt->delta_seq, 0);
+      break;
    default:
-      stat = sd->fsend("%ld %d %s%c%s%c%c%s%c", jcr->JobFiles,
-               ff_pkt->type, ff_pkt->fname, 0, attribs, 0, 0, attribsEx, 0);
+      stat = sd->fsend("%ld %d %s%c%s%c%c%s%c%u%c", jcr->JobFiles,
+                       ff_pkt->type, ff_pkt->fname, 0, attribs, 0, 0,
+                       attribsEx, 0, ff_pkt->delta_seq, 0);
       break;
    }
-   if (ff_pkt->type != FT_DELETED) {
+
+   if (!IS_FT_OBJECT(ff_pkt->type) && ff_pkt->type != FT_DELETED) {
       unstrip_path(ff_pkt);
    }
 
@@ -1200,8 +1441,10 @@ static bool do_strip(int count, char *in)
    while (*in && !IsPathSeparator(*in)) {    
       out++; in++;
    }
-   out++; in++;
-   numsep++;                     /* one separator seen */
+   if (*in) {                    /* Not at the end of the string */
+      out++; in++;
+      numsep++;                  /* one separator seen */
+   }
    for (stripped=0; stripped<count && *in; stripped++) {
       while (*in && !IsPathSeparator(*in)) {
          in++;                   /* skip chars */
@@ -1246,7 +1489,7 @@ void strip_path(FF_PKT *ff_pkt)
       pm_strcpy(ff_pkt->link_save, ff_pkt->link);
       Dmsg2(500, "strcpy link_save=%d link=%d\n", strlen(ff_pkt->link_save),
          strlen(ff_pkt->link));
-      sm_check(__FILE__, __LINE__, true);
+      Dsm_check(200);
    }
 
    /**
@@ -1285,6 +1528,40 @@ void unstrip_path(FF_PKT *ff_pkt)
       strcpy(ff_pkt->link, ff_pkt->link_save);
       Dmsg2(500, "strcpy link=%d link_save=%d\n", strlen(ff_pkt->link),
           strlen(ff_pkt->link_save));
-      sm_check(__FILE__, __LINE__, true);
+      Dsm_check(200);
+   }
+}
+
+static void close_vss_backup_session(JCR *jcr)
+{
+#if defined(WIN32_VSS)
+   /* STOP VSS ON WIN32 */
+   /* tell vss to close the backup session */
+   if (jcr->VSS) {
+      if (g_pVSSClient->CloseBackup()) {             
+         /* inform user about writer states */
+         for (int i=0; i<(int)g_pVSSClient->GetWriterCount(); i++) {
+            int msg_type = M_INFO;
+            if (g_pVSSClient->GetWriterState(i) < 1) {
+               msg_type = M_WARNING;
+               jcr->JobErrors++;
+            }
+            Jmsg(jcr, msg_type, 0, _("VSS Writer (BackupComplete): %s\n"), g_pVSSClient->GetWriterInfo(i));
+         }
+      }
+      /* Generate Job global writer metadata */
+      WCHAR *metadata = g_pVSSClient->GetMetadata();
+      if (metadata) {
+         FF_PKT *ff_pkt = jcr->ff;
+         ff_pkt->fname = (char *)"*all*"; /* for all plugins */
+         ff_pkt->type = FT_RESTORE_FIRST;
+         ff_pkt->LinkFI = 0;
+         ff_pkt->object_name = (char *)"job_metadata.xml";
+         ff_pkt->object = (char *)metadata;
+         ff_pkt->object_len = (wcslen(metadata) + 1) * sizeof(WCHAR);
+         ff_pkt->object_index = (int)time(NULL);
+         save_file(jcr, ff_pkt, true);
+     }
    }
+#endif
 }