]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/dird/fd_cmds.c
Integrate patch into latest version, which fixes bug #1882
[bacula/bacula] / bacula / src / dird / fd_cmds.c
index 762850e5be00aa8890e160d95343006cd39a034c..84e198de8b52b67b0fcc95e49b414d8e57d3c940 100644 (file)
@@ -1,29 +1,20 @@
 /*
-   Bacula® - The Network Backup Solution
-
-   Copyright (C) 2000-2009 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
-   License as published by the Free Software Foundation and included
-   in the file LICENSE.
-
-   This program is distributed in the hope that it will be useful, but
-   WITHOUT ANY WARRANTY; without even the implied warranty of
-   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
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301, USA.
-
-   Bacula® is a registered trademark of Kern Sibbald.
-   The licensor of Bacula is the Free Software Foundation Europe
-   (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
-   Switzerland, email:ftf@fsfeurope.org.
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2015 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is 
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
 */
 /*
  *
@@ -37,7 +28,6 @@
  *  Utility functions for sending info to File Daemon.
  *   These functions are used by both backup and verify.
  *
- *   Version $Id$
  */
 
 #include "bacula.h"
 const int dbglvl = 400;
 
 /* Commands sent to File daemon */
-static char filesetcmd[]  = "fileset%s\n"; /* set full fileset */
+static char filesetcmd[]  = "fileset%s%s\n"; /* set full fileset */
 static char jobcmd[]      = "JobId=%s Job=%s SDid=%u SDtime=%u Authorization=%s\n";
 /* Note, mtime_only is not used here -- implemented as file option */
-static char levelcmd[]    = "level = %s%s%s mtime_only=%d\n";
+static char levelcmd[]    = "level = %s%s%s mtime_only=%d %s%s\n";
 static char runscript[]   = "Run OnSuccess=%u OnFailure=%u AbortOnError=%u When=%u Command=%s\n";
 static char runbeforenow[]= "RunBeforeNow\n";
+static char bandwidthcmd[] = "setbandwidth=%lld Job=%s\n";
+static char component_info[] = "component_info\n";
 
 /* Responses received from File daemon */
 static char OKinc[]          = "2000 OK include\n";
@@ -60,6 +52,9 @@ static char OKjob[]          = "2000 OK Job";
 static char OKlevel[]        = "2000 OK level\n";
 static char OKRunScript[]    = "2000 OK RunScript\n";
 static char OKRunBeforeNow[] = "2000 OK RunBeforeNow\n";
+static char OKRestoreObject[] = "2000 OK ObjectRestored\n";
+static char OKComponentInfo[] = "2000 OK ComponentInfo\n";
+static char OKBandwidth[]    = "2000 OK Bandwidth\n";
 
 /* Forward referenced functions */
 static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd);
@@ -80,51 +75,58 @@ extern int FDConnectTimeout;
 int connect_to_file_daemon(JCR *jcr, int retry_interval, int max_retry_time,
                            int verbose)
 {
-   BSOCK   *fd = new_bsock();
+   BSOCK   *fd = jcr->file_bsock;
    char ed1[30];
    utime_t heart_beat;
 
+   if (!jcr->client) {
+      Jmsg(jcr, M_FATAL, 0, _("File daemon not defined for current Job\n"));
+      Dmsg0(10, "No Client defined for the job.\n");
+      return 0;
+   }
+
    if (jcr->client->heartbeat_interval) {
       heart_beat = jcr->client->heartbeat_interval;
-   } else {           
+   } else {
       heart_beat = director->heartbeat_interval;
    }
 
-   if (!jcr->file_bsock) {
+   if (!is_bsock_open(jcr->file_bsock)) {
       char name[MAX_NAME_LENGTH + 100];
+
+      if (!fd) {
+         fd = jcr->file_bsock = new_bsock();
+      }
       bstrncpy(name, _("Client: "), sizeof(name));
       bstrncat(name, jcr->client->name(), sizeof(name));
 
       fd->set_source_address(director->DIRsrc_addr);
       if (!fd->connect(jcr,retry_interval,max_retry_time, heart_beat, name, jcr->client->address,
            NULL, jcr->client->FDport, verbose)) {
-        fd->destroy();
-        fd = NULL;
-      }
-
-      if (fd == NULL) {
-         set_jcr_job_status(jcr, JS_ErrorTerminated);
+         fd->close();
+         jcr->setJobStatus(JS_ErrorTerminated);
          return 0;
       }
       Dmsg0(10, "Opened connection with File daemon\n");
-   } else {
-      fd = jcr->file_bsock;           /* use existing connection */
    }
    fd->res = (RES *)jcr->client;      /* save resource in BSOCK */
-   jcr->file_bsock = fd;
-   set_jcr_job_status(jcr, JS_Running);
+   jcr->setJobStatus(JS_Running);
 
    if (!authenticate_file_daemon(jcr)) {
-      set_jcr_job_status(jcr, JS_ErrorTerminated);
+      jcr->setJobStatus(JS_ErrorTerminated);
+      Dmsg0(10, "Authentication error with FD.\n");
       return 0;
    }
 
    /*
     * Now send JobId and authorization key
     */
+   if (jcr->sd_auth_key == NULL) {
+      jcr->sd_auth_key = bstrdup("dummy");
+   }
    fd->fsend(jobcmd, edit_int64(jcr->JobId, ed1), jcr->Job, jcr->VolSessionId,
-      jcr->VolSessionTime, jcr->sd_auth_key);
-   if (strcmp(jcr->sd_auth_key, "dummy") != 0) {
+             jcr->VolSessionTime, jcr->sd_auth_key);
+   if (!jcr->keep_sd_auth_key && strcmp(jcr->sd_auth_key, "dummy")) {
       memset(jcr->sd_auth_key, 0, strlen(jcr->sd_auth_key));
    }
    Dmsg1(100, ">filed: %s", fd->msg);
@@ -133,7 +135,7 @@ int connect_to_file_daemon(JCR *jcr, int retry_interval, int max_retry_time,
        if (strncmp(fd->msg, OKjob, strlen(OKjob)) != 0) {
           Jmsg(jcr, M_FATAL, 0, _("File daemon \"%s\" rejected Job command: %s\n"),
              jcr->client->hdr.name, fd->msg);
-          set_jcr_job_status(jcr, JS_ErrorTerminated);
+          jcr->setJobStatus(JS_ErrorTerminated);
           return 0;
        } else if (jcr->db) {
           CLIENT_DBR cr;
@@ -150,8 +152,8 @@ int connect_to_file_daemon(JCR *jcr, int retry_interval, int max_retry_time,
        }
    } else {
       Jmsg(jcr, M_FATAL, 0, _("FD gave bad response to JobId command: %s\n"),
-         bnet_strerror(fd));
-      set_jcr_job_status(jcr, JS_ErrorTerminated);
+         fd->bstrerror());
+      jcr->setJobStatus(JS_ErrorTerminated);
       return 0;
    }
    return 1;
@@ -173,6 +175,7 @@ void get_level_since_time(JCR *jcr, char *since, int since_len)
    utime_t now;
    utime_t last_full_time = 0;
    utime_t last_diff_time;
+   char prev_job[MAX_NAME_LENGTH];
 
    since[0] = 0;
    /* If job cloned and a since time already given, use it */
@@ -184,13 +187,13 @@ void get_level_since_time(JCR *jcr, char *since, int since_len)
    /* Make sure stime buffer is allocated */
    if (!jcr->stime) {
       jcr->stime = get_pool_memory(PM_MESSAGE);
-   } 
-   jcr->stime[0] = 0;
+   }
+   jcr->PrevJob[0] = jcr->stime[0] = 0;
    /*
     * Lookup the last FULL backup job to get the time/date for a
     * differential or incremental save.
     */
-   switch (jcr->get_JobLevel()) {
+   switch (jcr->getJobLevel()) {
    case L_DIFFERENTIAL:
    case L_INCREMENTAL:
       POOLMEM *stime = get_pool_memory(PM_MESSAGE);
@@ -201,21 +204,23 @@ void get_level_since_time(JCR *jcr, char *since, int since_len)
        * This is probably redundant, but some of the code below
        * uses jcr->stime, so don't remove unless you are sure.
        */
-      if (!db_find_job_start_time(jcr, jcr->db, &jcr->jr, &jcr->stime)) {
+      if (!db_find_job_start_time(jcr, jcr->db, &jcr->jr, &jcr->stime, jcr->PrevJob)) {
          do_full = true;
       }
-      have_full = db_find_last_job_start_time(jcr, jcr->db, &jcr->jr, &stime, L_FULL);
+      have_full = db_find_last_job_start_time(jcr, jcr->db, &jcr->jr,
+                                              &stime, prev_job, L_FULL);
       if (have_full) {
          last_full_time = str_to_utime(stime);
       } else {
          do_full = true;               /* No full, upgrade to one */
       }
-      Dmsg4(50, "have_full=%d do_full=%d now=%lld full_time=%lld\n", have_full, 
+      Dmsg4(50, "have_full=%d do_full=%d now=%lld full_time=%lld\n", have_full,
             do_full, now, last_full_time);
       /* Make sure the last diff is recent enough */
-      if (have_full && jcr->get_JobLevel() == L_INCREMENTAL && jcr->job->MaxDiffInterval > 0) {
+      if (have_full && jcr->getJobLevel() == L_INCREMENTAL && jcr->job->MaxDiffInterval > 0) {
          /* Lookup last diff job */
-         if (db_find_last_job_start_time(jcr, jcr->db, &jcr->jr, &stime, L_DIFFERENTIAL)) {
+         if (db_find_last_job_start_time(jcr, jcr->db, &jcr->jr,
+                                         &stime, prev_job, L_DIFFERENTIAL)) {
             last_diff_time = str_to_utime(stime);
             /* If no Diff since Full, use Full time */
             if (last_diff_time < last_full_time) {
@@ -242,25 +247,43 @@ void get_level_since_time(JCR *jcr, char *since, int since_len)
          Jmsg(jcr, M_INFO, 0, "%s", db_strerror(jcr->db));
          Jmsg(jcr, M_INFO, 0, _("No prior or suitable Full backup found in catalog. Doing FULL backup.\n"));
          bsnprintf(since, since_len, _(" (upgraded from %s)"),
-            level_to_str(jcr->get_JobLevel()));
-         jcr->set_JobLevel(jcr->jr.JobLevel = L_FULL);
+            level_to_str(jcr->getJobLevel()));
+         jcr->setJobLevel(jcr->jr.JobLevel = L_FULL);
        } else if (do_diff) {
          /* No recent diff job found, so upgrade this one to Diff */
          Jmsg(jcr, M_INFO, 0, _("No prior or suitable Differential backup found in catalog. Doing Differential backup.\n"));
          bsnprintf(since, since_len, _(" (upgraded from %s)"),
-            level_to_str(jcr->get_JobLevel()));
-         jcr->set_JobLevel(jcr->jr.JobLevel = L_DIFFERENTIAL);
+            level_to_str(jcr->getJobLevel()));
+         jcr->setJobLevel(jcr->jr.JobLevel = L_DIFFERENTIAL);
       } else {
          if (jcr->job->rerun_failed_levels) {
-            if (db_find_failed_job_since(jcr, jcr->db, &jcr->jr, jcr->stime, JobLevel)) {
-               Jmsg(jcr, M_INFO, 0, _("Prior failed job found in catalog. Upgrading to %s.\n"),
-                  level_to_str(JobLevel));
-               bsnprintf(since, since_len, _(" (upgraded from %s)"),
-                  level_to_str(jcr->get_JobLevel()));
-               jcr->set_JobLevel(jcr->jr.JobLevel = JobLevel);
-               jcr->jr.JobId = jcr->JobId;
-               break;
+
+            POOLMEM *etime = get_pool_memory(PM_MESSAGE);
+
+            /* Get the end time of our most recent successfull backup for this job */
+            /* This will be used to see if there have been any failures since then */
+           if (db_find_last_job_end_time(jcr, jcr->db, &jcr->jr, &etime, prev_job)) {
+
+              /* See if there are any failed Differential/Full backups since the completion */
+               /* of our last successful backup for this job                                 */
+               if (db_find_failed_job_since(jcr, jcr->db, &jcr->jr,
+                                         etime, JobLevel)) {
+                 /* If our job is an Incremental and we have a failed job then upgrade.              */
+                 /* If our job is a Differential and the failed job is a Full then upgrade.          */
+                 /* Otherwise there is no reason to upgrade.                                         */
+                if ((jcr->getJobLevel() == L_INCREMENTAL) || 
+                     ((jcr->getJobLevel() == L_DIFFERENTIAL) && (JobLevel == L_FULL))) {
+                    Jmsg(jcr, M_INFO, 0, _("Prior failed job found in catalog. Upgrading to %s.\n"),
+                       level_to_str(JobLevel));
+                    bsnprintf(since, since_len, _(" (upgraded from %s)"),
+                             level_to_str(jcr->getJobLevel()));
+                    jcr->setJobLevel(jcr->jr.JobLevel = JobLevel);
+                    jcr->jr.JobId = jcr->JobId;
+                    break;
+                 }
+               }
             }
+            free_pool_memory(etime);
          }
          bstrncpy(since, _(", since="), since_len);
          bstrncat(since, jcr->stime, since_len);
@@ -268,7 +291,8 @@ void get_level_since_time(JCR *jcr, char *since, int since_len)
       jcr->jr.JobId = jcr->JobId;
       break;
    }
-   Dmsg2(100, "Level=%c last start time=%s\n", jcr->get_JobLevel(), jcr->stime);
+   Dmsg3(100, "Level=%c last start time=%s job=%s\n",
+         jcr->getJobLevel(), jcr->stime, jcr->PrevJob);
 }
 
 static void send_since_time(JCR *jcr)
@@ -278,12 +302,26 @@ static void send_since_time(JCR *jcr)
    char ed1[50];
 
    stime = str_to_utime(jcr->stime);
-   fd->fsend(levelcmd, "", NT_("since_utime "), edit_uint64(stime, ed1), 0);
+   fd->fsend(levelcmd, "", NT_("since_utime "), edit_uint64(stime, ed1), 0,
+             NT_("prev_job="), jcr->PrevJob);
    while (bget_dirmsg(fd) >= 0) {  /* allow him to poll us to sync clocks */
       Jmsg(jcr, M_INFO, 0, "%s\n", fd->msg);
    }
 }
 
+bool send_bwlimit(JCR *jcr, const char *Job)
+{
+   BSOCK *fd = jcr->file_bsock;
+   if (jcr->FDVersion >= 4) {
+      fd->fsend(bandwidthcmd, jcr->max_bandwidth, Job);
+      if (!response(jcr, fd, OKBandwidth, "Bandwidth", DISPLAY_ERROR)) {
+         jcr->max_bandwidth = 0;      /* can't set bandwidth limit */
+         return false;
+      }
+   }
+   return true;
+}
+
 /*
  * Send level command to FD.
  * Used for backup jobs and estimate command.
@@ -291,39 +329,40 @@ static void send_since_time(JCR *jcr)
 bool send_level_command(JCR *jcr)
 {
    BSOCK   *fd = jcr->file_bsock;
-   const char *accurate = jcr->job->accurate?"accurate_":"";
+   const char *accurate = jcr->accurate?"accurate_":"";
    const char *not_accurate = "";
+   const char *rerunning = jcr->rerunning?" rerunning ":" ";
    /*
     * Send Level command to File daemon
     */
-   switch (jcr->get_JobLevel()) {
+   switch (jcr->getJobLevel()) {
    case L_BASE:
-      fd->fsend(levelcmd, not_accurate, "base", " ", 0);
+      fd->fsend(levelcmd, not_accurate, "base", rerunning, 0, "", "");
       break;
    /* L_NONE is the console, sending something off to the FD */
    case L_NONE:
    case L_FULL:
-      fd->fsend(levelcmd, not_accurate, "full", " ", 0);
+      fd->fsend(levelcmd, not_accurate, "full", rerunning, 0, "", "");
       break;
    case L_DIFFERENTIAL:
-      fd->fsend(levelcmd, accurate, "differential", " ", 0);
+      fd->fsend(levelcmd, accurate, "differential", rerunning, 0, "", "");
       send_since_time(jcr);
       break;
    case L_INCREMENTAL:
-      fd->fsend(levelcmd, accurate, "incremental", " ", 0);
+      fd->fsend(levelcmd, accurate, "incremental", rerunning, 0, "", "");
       send_since_time(jcr);
       break;
    case L_SINCE:
    default:
       Jmsg2(jcr, M_FATAL, 0, _("Unimplemented backup level %d %c\n"),
-         jcr->get_JobLevel(), jcr->get_JobLevel());
+         jcr->getJobLevel(), jcr->getJobLevel());
       return 0;
    }
    Dmsg1(120, ">filed: %s", fd->msg);
    if (!response(jcr, fd, OKlevel, "Level", DISPLAY_ERROR)) {
-      return 0;
+      return false;
    }
-   return 1;
+   return true;
 }
 
 /*
@@ -333,6 +372,7 @@ static bool send_fileset(JCR *jcr)
 {
    FILESET *fileset = jcr->fileset;
    BSOCK   *fd = jcr->file_bsock;
+   STORE   *store = jcr->wstore;
    int num;
    bool include = true;
 
@@ -354,11 +394,16 @@ static bool send_fileset(JCR *jcr)
             ie = fileset->exclude_items[i];
             fd->fsend("E\n");
          }
+         if (ie->ignoredir) {
+            fd->fsend("Z %s\n", ie->ignoredir);
+         }
          for (j=0; j<ie->num_opts; j++) {
             FOPTS *fo = ie->opts_list[j];
-            fd->fsend("O %s\n", fo->opts);
-
             bool enhanced_wild = false;
+            bool stripped_opts = false;
+            bool compress_disabled = false;
+            char newopts[MAX_FOPTS];
+
             for (k=0; fo->opts[k]!='\0'; k++) {
                if (fo->opts[k]=='W') {
                   enhanced_wild = true;
@@ -366,6 +411,41 @@ static bool send_fileset(JCR *jcr)
                }
             }
 
+            /*
+             * Strip out compression option Zn if disallowed
+             *  for this Storage.
+             * Strip out dedup option dn if old FD
+             */
+            bool strip_compress = store && !store->AllowCompress;
+            if (strip_compress || jcr->FDVersion >= 11) {
+               int j = 0;
+               for (k=0; fo->opts[k]!='\0'; k++) {
+                  /* Z compress option is followed by the single-digit compress level or 'o' */
+                  if (strip_compress && fo->opts[k]=='Z') {
+                     stripped_opts = true;
+                     compress_disabled = true;
+                     k++;                /* skip level */
+                  } else if (jcr->FDVersion < 11 && fo->opts[k]=='d') {
+                     stripped_opts = true;
+                     k++;              /* skip level */
+                  } else {
+                     newopts[j] = fo->opts[k];
+                     j++;
+                  }
+               }
+               newopts[j] = '\0';
+               if (compress_disabled) {
+                  Jmsg(jcr, M_INFO, 0,
+                      _("FD compression disabled for this Job because AllowCompress=No in Storage resource.\n") );
+               }
+            }
+            if (stripped_opts) {
+               /* Send the new trimmed option set without overwriting fo->opts */
+               fd->fsend("O %s\n", newopts);
+            } else {
+               /* Send the original options */
+               fd->fsend("O %s\n", fo->opts);
+            }
             for (k=0; k<fo->regex.size(); k++) {
                fd->fsend("R %s\n", fo->regex.get(k));
             }
@@ -399,9 +479,6 @@ static bool send_fileset(JCR *jcr)
             if (fo->plugin) {
                fd->fsend("G %s\n", fo->plugin);
             }
-            if (fo->ignoredir) {
-               bnet_fsend(fd, "Z %s\n", fo->ignoredir);
-            }
             if (fo->reader) {
                fd->fsend("D %s\n", fo->reader);
             }
@@ -439,7 +516,7 @@ static bool send_fileset(JCR *jcr)
    return true;
 
 bail_out:
-   set_jcr_job_status(jcr, JS_ErrorTerminated);
+   jcr->setJobStatus(JS_ErrorTerminated);
    return false;
 
 }
@@ -469,7 +546,8 @@ static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd)
       while (fgets(buf+optlen, sizeof(buf)-optlen, bpipe->rfd)) {
          fd->msglen = Mmsg(fd->msg, "%s", buf);
          Dmsg2(500, "Inc/exc len=%d: %s", fd->msglen, fd->msg);
-         if (!bnet_send(fd)) {
+         if (!fd->send()) {
+            close_bpipe(bpipe);
             Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
             return false;
          }
@@ -494,7 +572,8 @@ static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd)
       optlen = strlen(buf);
       while (fgets(buf+optlen, sizeof(buf)-optlen, ffd)) {
          fd->msglen = Mmsg(fd->msg, "%s", buf);
-         if (!bnet_send(fd)) {
+         if (!fd->send()) {
+            fclose(ffd);
             Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
             return false;
          }
@@ -515,7 +594,7 @@ static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd)
       break;
    }
    return true;
-}            
+}
 
 
 /*
@@ -525,13 +604,14 @@ bool send_include_list(JCR *jcr)
 {
    BSOCK *fd = jcr->file_bsock;
    if (jcr->fileset->new_include) {
-      fd->fsend(filesetcmd, jcr->fileset->enable_vss ? " vss=1" : "");
+      fd->fsend(filesetcmd,
+                jcr->fileset->enable_vss ? " vss=1" : "",
+                jcr->fileset->enable_snapshot ? " snap=1" : "");
       return send_fileset(jcr);
    }
    return true;
 }
 
-
 /*
  * Send exclude list to File daemon
  *   Under the new scheme, the Exclude list
@@ -543,43 +623,6 @@ bool send_exclude_list(JCR *jcr)
    return true;
 }
 
-
-/*
- * Send bootstrap file if any to the socket given (FD or SD).
- *  This is used for restore, verify VolumeToCatalog, and
- *  for migration.
- */
-bool send_bootstrap_file(JCR *jcr, BSOCK *sock)
-{
-   FILE *bs;
-   char buf[1000];
-   const char *bootstrap = "bootstrap\n";
-
-   Dmsg1(400, "send_bootstrap_file: %s\n", jcr->RestoreBootstrap);
-   if (!jcr->RestoreBootstrap) {
-      return true;
-   }
-   bs = fopen(jcr->RestoreBootstrap, "rb");
-   if (!bs) {
-      berrno be;
-      Jmsg(jcr, M_FATAL, 0, _("Could not open bootstrap file %s: ERR=%s\n"),
-         jcr->RestoreBootstrap, be.bstrerror());
-      set_jcr_job_status(jcr, JS_ErrorTerminated);
-      return false;
-   }
-   sock->fsend(bootstrap);
-   while (fgets(buf, sizeof(buf), bs)) {
-      sock->fsend("%s", buf);
-   }
-   sock->signal(BNET_EOD);
-   fclose(bs);
-   if (jcr->unlink_bsr) {
-      unlink(jcr->RestoreBootstrap);
-      jcr->unlink_bsr = false;
-   }                         
-   return true;
-}
-
 /* TODO: drop this with runscript.old_proto in bacula 1.42 */
 static char runbefore[]   = "RunBeforeJob %s\n";
 static char runafter[]    = "RunAfterJob %s\n";
@@ -591,10 +634,10 @@ int send_runscript_with_old_proto(JCR *jcr, int when, POOLMEM *msg)
    int ret;
    Dmsg1(120, "bdird: sending old runcommand to fd '%s'\n",msg);
    if (when & SCRIPT_Before) {
-      bnet_fsend(jcr->file_bsock, runbefore, msg);
+      jcr->file_bsock->fsend(runbefore, msg);
       ret = response(jcr, jcr->file_bsock, OKRunBefore, "ClientRunBeforeJob", DISPLAY_ERROR);
    } else {
-      bnet_fsend(jcr->file_bsock, runafter, msg);
+      jcr->file_bsock->fsend(runafter, msg);
       ret = response(jcr, jcr->file_bsock, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR);
    }
    return ret;
@@ -616,9 +659,9 @@ int send_runscripts_commands(JCR *jcr)
    int result;
 
    Dmsg0(120, "bdird: sending runscripts to fd\n");
-   
+
    foreach_alist(cmd, jcr->job->RunScripts) {
-      if (cmd->can_run_at_level(jcr->get_JobLevel()) && cmd->target) {
+      if (cmd->can_run_at_level(jcr->getJobLevel()) && cmd->target) {
          ehost = edit_job_codes(jcr, ehost, cmd->target, "");
          Dmsg2(200, "bdird: runscript %s -> %s\n", cmd->target, ehost);
 
@@ -627,13 +670,13 @@ int send_runscripts_commands(JCR *jcr)
             bash_spaces(msg);
 
             Dmsg1(120, "bdird: sending runscripts to fd '%s'\n", cmd->command);
-            
+
             /* TODO: remove this with bacula 1.42 */
             if (cmd->old_proto) {
                result = send_runscript_with_old_proto(jcr, cmd->when, msg);
 
             } else {
-               fd->fsend(runscript, cmd->on_success, 
+               fd->fsend(runscript, cmd->on_success,
                                     cmd->on_failure,
                                     cmd->fail_on_error,
                                     cmd->when,
@@ -642,7 +685,7 @@ int send_runscripts_commands(JCR *jcr)
                result = response(jcr, fd, OKRunScript, "RunScript", DISPLAY_ERROR);
                launch_before_cmd = true;
             }
-            
+
             if (!result) {
                goto bail_out;
             }
@@ -653,8 +696,8 @@ int send_runscripts_commands(JCR *jcr)
            send command to an other client
            }
          */
-      }        
-   } 
+      }
+   }
 
    /* Tell the FD to execute the ClientRunBeforeJob */
    if (launch_before_cmd) {
@@ -674,7 +717,152 @@ bail_out:
    return 0;
 }
 
+struct OBJ_CTX {
+   JCR *jcr;
+   int count;
+};
+
+static int restore_object_handler(void *ctx, int num_fields, char **row)
+{
+   OBJ_CTX *octx = (OBJ_CTX *)ctx;
+   JCR *jcr = octx->jcr;
+   BSOCK *fd;
 
+   fd = jcr->file_bsock;
+   if (jcr->is_job_canceled()) {
+      return 1;
+   }
+   /* Old File Daemon doesn't handle restore objects */
+   if (jcr->FDVersion < 3) {
+      Jmsg(jcr, M_WARNING, 0, _("Client \"%s\" may not be used to restore "
+                                "this job. Please upgrade your client.\n"),
+           jcr->client->name());
+      return 1;
+   }
+
+   if (jcr->FDVersion < 5) {    /* Old version without PluginName */
+      fd->fsend("restoreobject JobId=%s %s,%s,%s,%s,%s,%s\n",
+                row[0], row[1], row[2], row[3], row[4], row[5], row[6]);
+   } else {
+      /* bash spaces from PluginName */
+      bash_spaces(row[9]);
+      fd->fsend("restoreobject JobId=%s %s,%s,%s,%s,%s,%s,%s\n",
+                row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[9]);
+   }
+   Dmsg1(010, "Send obj hdr=%s", fd->msg);
+
+   fd->msglen = pm_strcpy(fd->msg, row[7]);
+   fd->send();                            /* send Object name */
+
+   Dmsg1(010, "Send obj: %s\n", fd->msg);
+
+//   fd->msglen = str_to_uint64(row[1]);   /* object length */
+//   Dmsg1(000, "obj size: %lld\n", (uint64_t)fd->msglen);
+
+   /* object */
+   db_unescape_object(jcr, jcr->db,
+                      row[8],                /* Object  */
+                      str_to_uint64(row[1]), /* Object length */
+                      &fd->msg, &fd->msglen);
+   fd->send();                           /* send object */
+   octx->count++;
+
+   if (debug_level > 100) {
+      for (int i=0; i < fd->msglen; i++)
+         if (!fd->msg[i])
+            fd->msg[i] = ' ';
+      Dmsg1(000, "Send obj: %s\n", fd->msg);
+   }
+
+   return 0;
+}
+
+/*
+ * Send the plugin Restore Objects, which allow the
+ *  plugin to get information early in the restore
+ *  process.  The RestoreObjects were created during
+ *  the backup by the plugin.
+ */
+bool send_restore_objects(JCR *jcr)
+{
+   char ed1[50];
+   POOL_MEM query(PM_MESSAGE);
+   BSOCK *fd;
+   OBJ_CTX octx;
+
+   if (!jcr->JobIds || !jcr->JobIds[0]) {
+      return true;
+   }
+   octx.jcr = jcr;
+   octx.count = 0;
+
+   /* restore_object_handler is called for each file found */
+
+   /* send restore objects for all jobs involved  */
+   Mmsg(query, get_restore_objects, jcr->JobIds, FT_RESTORE_FIRST);
+   db_sql_query(jcr->db, query.c_str(), restore_object_handler, (void *)&octx);
+
+   /* send config objects for the current restore job */
+   Mmsg(query, get_restore_objects,
+        edit_uint64(jcr->JobId, ed1), FT_PLUGIN_CONFIG_FILLED);
+   db_sql_query(jcr->db, query.c_str(), restore_object_handler, (void *)&octx);
+
+   /*
+    * Send to FD only if we have at least one restore object.
+    * This permits backward compatibility with older FDs.
+    */
+   if (octx.count > 0) {
+      fd = jcr->file_bsock;
+      fd->fsend("restoreobject end\n");
+      if (!response(jcr, fd, OKRestoreObject, "RestoreObject", DISPLAY_ERROR)) {
+         Jmsg(jcr, M_FATAL, 0, _("RestoreObject failed.\n"));
+         return false;
+      }
+   }
+   return true;
+}
+
+/*
+ * Send the plugin a list of component info files.  These
+ *  were files that were created during the backup for
+ *  the VSS plugin.  The list is a list of those component
+ *  files that have been chosen for restore.  We
+ *  send them before the Restore Objects.
+ */
+bool send_component_info(JCR *jcr)
+{
+   BSOCK *fd;
+   char buf[2000];
+   bool ok = true;
+
+   if (!jcr->component_fd) {
+      return true;           /* nothing to send */
+   }
+   /* Don't send if old version FD */
+   if (jcr->FDVersion < 6) {
+      goto bail_out;
+   }
+
+   rewind(jcr->component_fd);
+   fd = jcr->file_bsock;
+   fd->fsend(component_info);
+   while (fgets(buf, sizeof(buf), jcr->component_fd)) {
+      fd->fsend("%s", buf);
+      Dmsg1(050, "Send component_info to FD: %s\n", buf);
+   }
+   fd->signal(BNET_EOD);
+   if (!response(jcr, fd, OKComponentInfo, "ComponentInfo", DISPLAY_ERROR)) {
+      Jmsg(jcr, M_FATAL, 0, _("ComponentInfo failed.\n"));
+      ok = false;
+   }
+
+bail_out:
+   fclose(jcr->component_fd);
+   jcr->component_fd = NULL;
+   unlink(jcr->component_fname);
+   free_and_null_pool_memory(jcr->component_fname);
+   return ok;
+}
 
 /*
  * Read the attributes from the File daemon for
@@ -702,10 +890,17 @@ int get_attributes_and_put_in_catalog(JCR *jcr)
       char *p, *fn;
       char Digest[MAXSTRING];      /* either Verify opts or MD5/SHA1 digest */
 
+      /* Stop here if canceled */
+      if (jcr->is_job_canceled()) {
+         jcr->cached_attribute = false;
+         return 0;
+      }
+
       if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Digest)) != 3) {
          Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
-         set_jcr_job_status(jcr, JS_ErrorTerminated);
+         jcr->setJobStatus(JS_ErrorTerminated);
+         jcr->cached_attribute = false;
          return 0;
       }
       p = fd->msg;
@@ -724,6 +919,7 @@ int get_attributes_and_put_in_catalog(JCR *jcr)
             if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
                Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
             }
+            jcr->cached_attribute = false;
          }
          /* Any cached attr is flushed so we can reuse jcr->attr and jcr->ar */
          fn = jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
@@ -745,6 +941,7 @@ int get_attributes_and_put_in_catalog(JCR *jcr)
          ar->FilenameId = 0;
          ar->Digest = NULL;
          ar->DigestType = CRYPTO_DIGEST_NONE;
+         ar->DeltaSeq = 0;
          jcr->cached_attribute = true;
 
          Dmsg2(dbglvl, "dird<filed: stream=%d %s\n", stream, jcr->fname);
@@ -771,19 +968,20 @@ int get_attributes_and_put_in_catalog(JCR *jcr)
       jcr->jr.JobFiles = jcr->JobFiles = file_index;
       jcr->jr.LastIndex = file_index;
    }
-   if (is_bnet_error(fd)) {
+   if (fd->is_error()) {
       Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
             fd->bstrerror());
+      jcr->cached_attribute = false;
       return 0;
    }
    if (jcr->cached_attribute) {
-      Dmsg3(dbglvl, "Cached attr with digest. Stream=%d fname=%s attr=%s\n", ar->Stream,            
+      Dmsg3(dbglvl, "Cached attr with digest. Stream=%d fname=%s attr=%s\n", ar->Stream,
          ar->fname, ar->attr);
       if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
          Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
       }
-      jcr->cached_attribute = false; 
+      jcr->cached_attribute = false;
    }
-   set_jcr_job_status(jcr, JS_Terminated);
+   jcr->setJobStatus(JS_Terminated);
    return 1;
 }