]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/filed/job.c
Stop restore if job is canceled
[bacula/bacula] / bacula / src / filed / job.c
index 94005ffe872f88015b218a58aa7cfab65c64ec65..41b9c11c2d12092a5890c21c75a86356edae4242 100644 (file)
@@ -1,12 +1,12 @@
 /*
    Bacula® - The Network Backup Solution
 
-   Copyright (C) 2000-2009 Free Software Foundation Europe e.V.
+   Copyright (C) 2000-2010 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.
@@ -30,8 +30,6 @@
  *
  *    Kern Sibbald, October MM
  *
- *   Version $Id$
- *
  */
 
 #include "bacula.h"
 #include "vss.h"
 
 static pthread_mutex_t vss_mutex = PTHREAD_MUTEX_INITIALIZER;
-static int enable_vss;
+static int enable_vss = 0;
+#endif
+
+/*
+ * As Windows saves ACLs as part of the standard backup stream
+ * we just pretend here that is has implicit acl support.
+ */
+#if defined(HAVE_ACL) || defined(HAVE_WIN32)
+const bool have_acl = true;
+#else
+const bool have_acl = false;
+#endif
+
+#if defined(HAVE_XATTR)
+const bool have_xattr = true;
+#else
+const bool have_xattr = false;
 #endif
 
 extern CLIENT *me;                    /* our client resource */
@@ -63,6 +77,7 @@ static int fileset_cmd(JCR *jcr);
 static int level_cmd(JCR *jcr);
 static int verify_cmd(JCR *jcr);
 static int restore_cmd(JCR *jcr);
+static int end_restore_cmd(JCR *jcr);
 static int storage_cmd(JCR *jcr);
 static int session_cmd(JCR *jcr);
 static int response(JCR *jcr, BSOCK *sd, char *resp, const char *cmd);
@@ -74,7 +89,11 @@ static int runbefore_cmd(JCR *jcr);
 static int runafter_cmd(JCR *jcr);
 static int runbeforenow_cmd(JCR *jcr);
 static void set_options(findFOPTS *fo, const char *opts);
-
+static void set_storage_auth_key(JCR *jcr, char *key);
+static int sm_dump_cmd(JCR *jcr);
+#ifdef DEVELOPER
+static int exit_cmd(JCR *jcr);
+#endif
 
 /* Exported functions */
 
@@ -97,6 +116,7 @@ static struct s_cmds cmds[] = {
    {"JobId=",       job_cmd,       0},
    {"level = ",     level_cmd,     0},
    {"restore",      restore_cmd,   0},
+   {"endrestore",   end_restore_cmd, 0},
    {"session",      session_cmd,   0},
    {"status",       status_cmd,    1},
    {".status",      qstatus_cmd,   1},
@@ -108,12 +128,17 @@ static struct s_cmds cmds[] = {
    {"RunAfterJob",  runafter_cmd,  0},
    {"Run",          runscript_cmd, 0},
    {"accurate",     accurate_cmd,  0},
+   {"sm_dump",      sm_dump_cmd, 0},
+#ifdef DEVELOPER
+   {"exit",         exit_cmd, 0},
+#endif
    {NULL,       NULL}                  /* list terminator */
 };
 
 /* Commands received from director that need scanning */
 static char jobcmd[]      = "JobId=%d Job=%127s SDid=%d SDtime=%d Authorization=%100s";
-static char storaddr[]    = "storage address=%s port=%d ssl=%d";
+static char storaddr[]    = "storage address=%s port=%d ssl=%d Authorization=%100s";
+static char storaddr_v1[] = "storage address=%s port=%d ssl=%d";
 static char sessioncmd[]  = "session %127s %ld %ld %ld %ld %ld %ld\n";
 static char restorecmd[]  = "restore replace=%c prelinks=%d where=%s\n";
 static char restorecmd1[] = "restore replace=%c prelinks=%d where=\n";
@@ -129,7 +154,7 @@ static char errmsg[]      = "2999 Invalid command\n";
 static char no_auth[]     = "2998 No Authorization\n";
 static char invalid_cmd[] = "2997 Invalid command for a Director with Monitor directive enabled.\n";
 static char OKinc[]       = "2000 OK include\n";
-static char OKest[]       = "2000 OK estimate files=%u bytes=%s\n";
+static char OKest[]       = "2000 OK estimate files=%s bytes=%s\n";
 static char OKlevel[]     = "2000 OK level\n";
 static char OKbackup[]    = "2000 OK backup\n";
 static char OKbootstrap[] = "2000 OK bootstrap\n";
@@ -137,6 +162,7 @@ static char OKverify[]    = "2000 OK verify\n";
 static char OKrestore[]   = "2000 OK restore\n";
 static char OKsession[]   = "2000 OK session\n";
 static char OKstore[]     = "2000 OK storage\n";
+static char OKstoreend[]  = "2000 OK storage end\n";
 static char OKjob[]       = "2000 OK Job %s (%s) %s,%s,%s";
 static char OKsetdebug[]  = "2000 OK setdebug=%d\n";
 static char BADjob[]      = "2901 Bad Job\n";
@@ -196,7 +222,7 @@ static char read_close[]   = "read close session %d\n";
  *  5. FD gets/runs ClientRunBeforeJob and sends ClientRunAfterJob
  *  6. Dir sends include/exclude
  *  7. FD sends data to SD
- *  8. SD/FD disconnects while SD despools data and attributes (optionnal)
+ *  8. SD/FD disconnects while SD despools data and attributes (optional)
  *  9. FD runs ClientRunAfterJob
  */
 
@@ -206,6 +232,7 @@ void *handle_client_request(void *dirp)
    bool found, quit;
    JCR *jcr;
    BSOCK *dir = (BSOCK *)dirp;
+   const char jobname[12] = "*Director*";
 
    jcr = new_jcr(sizeof(JCR), filed_free_jcr); /* create JCR */
    jcr->dir_bsock = dir;
@@ -216,6 +243,7 @@ void *handle_client_request(void *dirp)
    jcr->last_fname[0] = 0;
    jcr->client_name = get_memory(strlen(my_name) + 1);
    pm_strcpy(jcr->client_name, my_name);
+   bstrncpy(jcr->Job, jobname, sizeof(jobname));  /* dummy */
    jcr->crypto.pki_sign = me->pki_sign;
    jcr->crypto.pki_encrypt = me->pki_encrypt;
    jcr->crypto.pki_keypair = me->pki_keypair;
@@ -320,13 +348,13 @@ void *handle_client_request(void *dirp)
             fo->base.destroy();
             fo->fstype.destroy();
             fo->drivetype.destroy();
-            if (fo->ignoredir != NULL) {
-               free(fo->ignoredir);
-            }
          }
          incexe->opts_list.destroy();
          incexe->name_list.destroy();
          incexe->plugin_list.destroy();
+         if (incexe->ignoredir) {
+            free(incexe->ignoredir);
+         }
       }
       fileset->include_list.destroy();
 
@@ -349,6 +377,9 @@ void *handle_client_request(void *dirp)
          incexe->opts_list.destroy();
          incexe->name_list.destroy();
          incexe->plugin_list.destroy();
+         if (incexe->ignoredir) {
+            free(incexe->ignoredir);
+         }
       }
       fileset->exclude_list.destroy();
       free(fileset);
@@ -361,10 +392,29 @@ void *handle_client_request(void *dirp)
    free_jcr(jcr);                     /* destroy JCR record */
    Dmsg0(100, "Done with free_jcr\n");
    Dsm_check(1);
+   garbage_collect_memory_pool();
    return NULL;
 }
 
-/*
+static int sm_dump_cmd(JCR *jcr)
+{
+   close_memory_pool();
+   sm_dump(false, true);
+   jcr->dir_bsock->fsend("2000 sm_dump OK\n");
+   return 1;
+}
+
+#ifdef DEVELOPER
+static int exit_cmd(JCR *jcr)
+{
+   jcr->dir_bsock->fsend("2000 exit OK\n");
+   terminate_filed(0);
+   return 0;
+}
+#endif
+
+
+/**
  * Hello from Director he must identify himself and provide his
  *  password.
  */
@@ -395,7 +445,7 @@ static int cancel_cmd(JCR *jcr)
          if (cjcr->store_bsock) {
             cjcr->store_bsock->set_timed_out();
             cjcr->store_bsock->set_terminated();
-            pthread_kill(cjcr->my_thread_id, TIMEOUT_SIGNAL);
+            cjcr->my_thread_send_signal(TIMEOUT_SIGNAL);
          }
          generate_plugin_event(cjcr, bEventCancelCommand, NULL);
          set_jcr_job_status(cjcr, JS_Canceled);
@@ -434,7 +484,7 @@ static int setdebug_cmd(JCR *jcr)
 static int estimate_cmd(JCR *jcr)
 {
    BSOCK *dir = jcr->dir_bsock;
-   char ed2[50];
+   char ed1[50], ed2[50];
 
    if (sscanf(dir->msg, estimatecmd, &jcr->listing) != 1) {
       pm_strcpy(jcr->errmsg, dir->msg);
@@ -443,7 +493,7 @@ static int estimate_cmd(JCR *jcr)
       return 0;
    }
    make_estimate(jcr);
-   dir->fsend(OKest, jcr->num_files_examined,
+   dir->fsend(OKest, edit_uint64_with_commas(jcr->num_files_examined, ed1),
       edit_uint64_with_commas(jcr->JobBytes, ed2));
    dir->signal(BNET_EOD);
    return 1;
@@ -455,20 +505,18 @@ static int estimate_cmd(JCR *jcr)
 static int job_cmd(JCR *jcr)
 {
    BSOCK *dir = jcr->dir_bsock;
-   POOLMEM *sd_auth_key;
+   POOL_MEM sd_auth_key(PM_MESSAGE);
+   sd_auth_key.check_size(dir->msglen);
 
-   sd_auth_key = get_memory(dir->msglen);
    if (sscanf(dir->msg, jobcmd,  &jcr->JobId, jcr->Job,
-              &jcr->VolSessionId, &jcr->VolSessionTime,
-              sd_auth_key) != 5) {
+              &jcr->VolSessionId, &jcr->VolSessionTime, 
+              sd_auth_key.c_str()) != 5) {
       pm_strcpy(jcr->errmsg, dir->msg);
       Jmsg(jcr, M_FATAL, 0, _("Bad Job Command: %s"), jcr->errmsg);
       dir->fsend(BADjob);
-      free_pool_memory(sd_auth_key);
       return 0;
    }
-   jcr->sd_auth_key = bstrdup(sd_auth_key);
-   free_pool_memory(sd_auth_key);
+   set_storage_auth_key(jcr, sd_auth_key.c_str());
    Dmsg2(120, "JobId=%d Auth=%s\n", jcr->JobId, jcr->sd_auth_key);
    Mmsg(jcr->errmsg, "JobId=%d Job=%s", jcr->JobId, jcr->Job);
    new_plugins(jcr);                  /* instantiate plugins for this jcr */
@@ -641,8 +689,8 @@ static findFOPTS *start_options(FF_PKT *ff)
  * Add fname to include/exclude fileset list. First check for
  * | and < and if necessary perform command.
  */
-static void add_file_to_fileset(JCR *jcr, const char *fname, findFILESET *fileset,
-                                bool is_file)
+void add_file_to_fileset(JCR *jcr, const char *fname, findFILESET *fileset,
+                         bool is_file)
 {
    char *p;
    BPIPE *bpipe;
@@ -718,6 +766,21 @@ static void add_file_to_fileset(JCR *jcr, const char *fname, findFILESET *filese
    }
 }
 
+findFILESET *new_exclude(JCR *jcr)
+{
+   FF_PKT *ff = jcr->ff;
+   findFILESET *fileset = ff->fileset;
+
+   /* New exclude */
+   fileset->incexe = (findINCEXE *)malloc(sizeof(findINCEXE));
+   memset(fileset->incexe, 0, sizeof(findINCEXE));
+   fileset->incexe->opts_list.init(1, true);
+   fileset->incexe->name_list.init();
+   fileset->incexe->plugin_list.init();
+   fileset->exclude_list.append(fileset->incexe);
+   return fileset;
+}
+
 
 static void add_fileset(JCR *jcr, const char *item)
 {
@@ -768,13 +831,7 @@ static void add_fileset(JCR *jcr, const char *item)
       fileset->include_list.append(fileset->incexe);
       break;
    case 'E':
-      /* New exclude */
-      fileset->incexe = (findINCEXE *)malloc(sizeof(findINCEXE));
-      memset(fileset->incexe, 0, sizeof(findINCEXE));
-      fileset->incexe->opts_list.init(1, true);
-      fileset->incexe->name_list.init();
-      fileset->incexe->plugin_list.init();
-      fileset->exclude_list.append(fileset->incexe);
+      fileset = new_exclude(jcr);
       break;
    case 'N':
       state = state_none;
@@ -856,9 +913,8 @@ static void add_fileset(JCR *jcr, const char *item)
       state = state_options;
       break;
    case 'Z':
-      current_opts = start_options(ff);
-      current_opts->ignoredir = bstrdup(item);
-      state = state_options;
+      state = state_include;
+      fileset->incexe->ignoredir = bstrdup(item);
       break;
    case 'D':
       current_opts = start_options(ff);
@@ -921,9 +977,9 @@ static bool term_fileset(JCR *jcr)
          for (k=0; k<fo->drivetype.size(); k++) {
             Dmsg1(400, "XD %s\n", (char *)fo->drivetype.get(k));
          }
-         if (fo->ignoredir) {
-            Dmsg1(400, "Z %s\n", fo->ignoredir);
-         }
+      }
+      if (incexe->ignoredir) {
+         Dmsg1(400, "Z %s\n", incexe->ignoredir);
       }
       dlistString *node;
       foreach_dlist(node, &incexe->name_list) {
@@ -1092,6 +1148,16 @@ static void set_options(findFOPTS *fo, const char *opts)
          }
          fo->AccurateOpts[j] = 0;
          break;
+      case 'J':                  /* Basejob options */
+         /* Copy BaseJob Options */
+         for (j=0; *p && *p != ':'; p++) {
+            fo->BaseJobOpts[j] = *p;
+            if (j < (int)sizeof(fo->BaseJobOpts) - 1) {
+               j++;
+            }
+         }
+         fo->BaseJobOpts[j] = 0;
+         break;
       case 'P':                  /* strip path */
          /* Get integer */
          p++;                    /* skip P */
@@ -1236,6 +1302,8 @@ static int level_cmd(JCR *jcr)
 
    level = get_memory(dir->msglen+1);
    Dmsg1(100, "level_cmd: %s", dir->msg);
+
+   /* keep compatibility with older directors */
    if (strstr(dir->msg, "accurate")) {
       jcr->accurate = true;
    }
@@ -1264,7 +1332,7 @@ static int level_cmd(JCR *jcr)
       buf = get_memory(dir->msglen+1);
       utime_t since_time, adj;
       btime_t his_time, bt_start, rt=0, bt_adj=0;
-      if (jcr->get_JobLevel() == L_NONE) {
+      if (jcr->getJobLevel() == L_NONE) {
          jcr->set_JobLevel(L_SINCE);     /* if no other job level set, do it now */
       }
       if (sscanf(dir->msg, "level = since_utime %s mtime_only=%d",
@@ -1310,11 +1378,11 @@ static int level_cmd(JCR *jcr)
          } else {
             type = M_INFO;
          }
-         Jmsg(jcr, type, 0, _("DIR and FD clocks differ by %d seconds, FD automatically compensating.\n"), adj);
+         Jmsg(jcr, type, 0, _("DIR and FD clocks differ by %lld seconds, FD automatically compensating.\n"), adj);
       }
       dir->signal(BNET_EOD);
 
-      Dmsg2(100, "adj = %d since_time=%lld\n", (int)adj, since_time);
+      Dmsg2(100, "adj=%lld since_time=%lld\n", adj, since_time);
       jcr->incremental = 1;           /* set incremental or decremental backup */
       jcr->mtime = since_time;        /* set since time */
       generate_plugin_event(jcr, bEventSince, (void *)(time_t)jcr->mtime);
@@ -1327,7 +1395,7 @@ static int level_cmd(JCR *jcr)
    if (buf) {
       free_memory(buf);
    }
-   generate_plugin_event(jcr, bEventLevel, (void *)jcr->get_JobLevel());
+   generate_plugin_event(jcr, bEventLevel, (void *)jcr->getJobLevel());
    return dir->fsend(OKlevel);
 
 bail_out:
@@ -1360,6 +1428,36 @@ static int session_cmd(JCR *jcr)
    return dir->fsend(OKsession);
 }
 
+static void set_storage_auth_key(JCR *jcr, char *key)
+{
+   /* if no key don't update anything */
+   if (!*key) {                
+      return;
+   }
+
+   /* We can be contacting multiple storage daemons.
+    * So, make sure that any old jcr->store_bsock is cleaned up. 
+    */
+   if (jcr->store_bsock) {
+      jcr->store_bsock->destroy();
+      jcr->store_bsock = NULL;
+   }
+
+   /* We can be contacting multiple storage daemons.
+    *   So, make sure that any old jcr->sd_auth_key is cleaned up. 
+    */
+   if (jcr->sd_auth_key) {
+      /* If we already have a Authorization key, director can do multi
+       * storage restore
+       */
+      Dmsg0(5, "set multi_restore=true\n");
+      jcr->multi_restore = true;
+      bfree(jcr->sd_auth_key);
+   }
+
+   jcr->sd_auth_key = bstrdup(key);
+}
+
 /*
  * Get address of storage daemon from Director
  *
@@ -1368,20 +1466,39 @@ static int storage_cmd(JCR *jcr)
 {
    int stored_port;                /* storage daemon port */
    int enable_ssl;                 /* enable ssl to sd */
+   POOL_MEM sd_auth_key(PM_MESSAGE);
    BSOCK *dir = jcr->dir_bsock;
-   BSOCK *sd;                         /* storage daemon bsock */
+   BSOCK *sd = new_bsock();        /* storage daemon bsock */
+
 
    Dmsg1(100, "StorageCmd: %s", dir->msg);
-   if (sscanf(dir->msg, storaddr, &jcr->stored_addr, &stored_port, &enable_ssl) != 3) {
-      pm_strcpy(jcr->errmsg, dir->msg);
-      Jmsg(jcr, M_FATAL, 0, _("Bad storage command: %s"), jcr->errmsg);
-      goto bail_out;
+   sd_auth_key.check_size(dir->msglen);
+   if (sscanf(dir->msg, storaddr, &jcr->stored_addr, &stored_port, 
+              &enable_ssl, sd_auth_key.c_str()) != 4)
+   {
+      if (sscanf(dir->msg, storaddr_v1, &jcr->stored_addr,
+                 &stored_port, &enable_ssl) != 3)
+      {
+         pm_strcpy(jcr->errmsg, dir->msg);
+         Jmsg(jcr, M_FATAL, 0, _("Bad storage command: %s"), jcr->errmsg);
+         goto bail_out;
+      }
    }
-   Dmsg3(110, "Open storage: %s:%d ssl=%d\n", jcr->stored_addr, stored_port, enable_ssl);
+
+   set_storage_auth_key(jcr, sd_auth_key.c_str());
+
+   Dmsg3(110, "Open storage: %s:%d ssl=%d\n", jcr->stored_addr, stored_port, 
+         enable_ssl);
    /* Open command communications with Storage daemon */
    /* Try to connect for 1 hour at 10 second intervals */
-   sd = bnet_connect(jcr, 10, (int)me->SDConnectTimeout, me->heartbeat_interval,
-                      _("Storage daemon"), jcr->stored_addr, NULL, stored_port, 1);
+
+   sd->set_source_address(me->FDsrc_addr);
+   if (!sd->connect(jcr, 10, (int)me->SDConnectTimeout, me->heartbeat_interval,
+                _("Storage daemon"), jcr->stored_addr, NULL, stored_port, 1)) {
+     sd->destroy();
+     sd = NULL;
+   }
+
    if (sd == NULL) {
       Jmsg(jcr, M_FATAL, 0, _("Failed to connect to Storage daemon: %s:%d\n"),
           jcr->stored_addr, stored_port);
@@ -1404,8 +1521,8 @@ static int storage_cmd(JCR *jcr)
    return dir->fsend(OKstore);
 
 bail_out:
-      dir->fsend(BADcmd, "storage");
-      return 0;
+   dir->fsend(BADcmd, "storage");
+   return 0;
 
 }
 
@@ -1431,6 +1548,19 @@ static int backup_cmd(JCR *jcr)
    }
 #endif
 
+   /*
+    * Validate some options given to the backup make sense for the compiled in
+    * options of this filed.
+    */
+   if (jcr->ff->flags & FO_ACL && !have_acl) {
+      Jmsg(jcr, M_FATAL, 0, _("ACL support not configured for your machine.\n"));
+      goto cleanup;
+   }
+   if (jcr->ff->flags & FO_XATTR && !have_xattr) {
+      Jmsg(jcr, M_FATAL, 0, _("XATTR support not configured for your machine.\n"));
+      goto cleanup;
+   }
+
    set_jcr_job_status(jcr, JS_Blocked);
    jcr->set_JobType(JT_BACKUP);
    Dmsg1(100, "begin backup ff=%p\n", jcr->ff);
@@ -1490,8 +1620,8 @@ static int backup_cmd(JCR *jcr)
         if (get_win32_driveletters(jcr->ff, szWinDriveLetters)) {
             Jmsg(jcr, M_INFO, 0, _("Generate VSS snapshots. Driver=\"%s\", Drive(s)=\"%s\"\n"), g_pVSSClient->GetDriverName(), szWinDriveLetters);
             if (!g_pVSSClient->CreateSnapshots(szWinDriveLetters)) {               
-               Jmsg(jcr, M_WARNING, 0, _("Generate VSS snapshots failed.\n"));
-               jcr->JobErrors++;
+               berrno be;
+               Jmsg(jcr, M_FATAL, 0, _("Generate VSS snapshots failed. ERR=%s\n"), be.bstrerror());
             } else {
                /* tell user if snapshot creation of a specific drive failed */
                int i;
@@ -1530,8 +1660,8 @@ static int backup_cmd(JCR *jcr)
       Dmsg0(110, "Error in blast_data.\n");
    } else {
       set_jcr_job_status(jcr, JS_Terminated);
-
-      if (jcr->JobStatus != JS_Terminated) {
+      /* Note, the above set status will not override an error */
+      if (!(jcr->JobStatus == JS_Terminated || jcr->JobStatus == JS_Warnings)) {
          bnet_suppress_error_messages(sd, 1);
          goto cleanup;                /* bail out now */
       }
@@ -1567,7 +1697,7 @@ static int backup_cmd(JCR *jcr)
          Jmsg(jcr, M_FATAL, 0, _("Append Close with SD failed.\n"));
          goto cleanup;
       }
-      if (SDJobStatus != JS_Terminated) {
+      if (!(SDJobStatus == JS_Terminated || SDJobStatus == JS_Warnings)) {
          Jmsg(jcr, M_FATAL, 0, _("Bad status %d returned from Storage Daemon.\n"),
             SDJobStatus);
       }
@@ -1589,6 +1719,7 @@ cleanup:
             Jmsg(jcr, msg_type, 0, _("VSS Writer (BackupComplete): %s\n"), g_pVSSClient->GetWriterInfo(i));
          }
       }
+      Win32ConvCleanupCache();
       V(vss_mutex);
    }
 #endif
@@ -1631,12 +1762,12 @@ static int verify_cmd(JCR *jcr)
    dir->fsend(OKverify);
 
    generate_daemon_event(jcr, "JobStart");
-   generate_plugin_event(jcr, bEventLevel, (void *)jcr->get_JobLevel());
+   generate_plugin_event(jcr, bEventLevel, (void *)jcr->getJobLevel());
    generate_plugin_event(jcr, bEventStartVerifyJob);
 
    Dmsg1(110, "filed>dird: %s", dir->msg);
 
-   switch (jcr->get_JobLevel()) {
+   switch (jcr->getJobLevel()) {
    case L_VERIFY_INIT:
    case L_VERIFY_CATALOG:
       do_verify(jcr);
@@ -1769,12 +1900,33 @@ static int restore_cmd(JCR *jcr)
    sd->signal(BNET_TERMINATE);
 
 bail_out:
+   bfree_and_null(jcr->where);
 
    if (jcr->JobErrors) {
       set_jcr_job_status(jcr, JS_ErrorTerminated);
    }
 
    Dmsg0(130, "Done in job.c\n");
+
+   int ret;
+   if (jcr->multi_restore) {
+      dir->fsend(OKstoreend);
+      ret = 1;     /* we continue the loop, waiting for next part */
+   } else {
+      end_restore_cmd(jcr);
+      ret = 0;     /* we stop here */
+   }
+
+   if (job_canceled(jcr)) {
+      ret = 0;     /* we stop here */
+   }
+
+   return ret;
+}
+
+static int end_restore_cmd(JCR *jcr) 
+{
+   Dmsg0(5, "end_restore_cmd\n");
    generate_plugin_event(jcr, bEventEndRestoreJob);
    return 0;                          /* return and terminate command loop */
 }