]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/dird/job.c
Restore win32 dir from Branch-5.2 and update it
[bacula/bacula] / bacula / src / dird / job.c
index fa667d40dbab3c59b0104c7220bc37e9ae297d5e..c3833ec6577b2b5a3fe3bd166362b0a7e9898ab2 100644 (file)
@@ -1,24 +1,25 @@
 /*
-   Bacula® - The Network Backup Solution
+   Bacula(R) - The Network Backup Solution
 
-   Copyright (C) 2000-2014 Free Software Foundation Europe e.V.
+   Copyright (C) 2000-2017 Kern Sibbald
 
-   The main author of Bacula is Kern Sibbald, with contributions from many
-   others, a complete list can be found in the file AUTHORS.
+   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.
 
-   Bacula® is a registered trademark of Kern Sibbald.
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
 */
 /*
- *
  *   Bacula Director Job processing routines
  *
  *     Kern Sibbald, October MM
- *
  */
 
 #include "bacula.h"
@@ -113,19 +114,25 @@ bool setup_job(JCR *jcr)
     */
    Dmsg0(100, "Open database\n");
    jcr->db = db_init_database(jcr, jcr->catalog->db_driver, jcr->catalog->db_name,
-                              jcr->catalog->db_user, jcr->catalog->db_password,
-                              jcr->catalog->db_address, jcr->catalog->db_port,
-                              jcr->catalog->db_socket, jcr->catalog->mult_db_connections,
-                              jcr->catalog->disable_batch_insert);
+                jcr->catalog->db_user, jcr->catalog->db_password,
+                jcr->catalog->db_address, jcr->catalog->db_port,
+                jcr->catalog->db_socket, jcr->catalog->db_ssl_mode,
+                jcr->catalog->db_ssl_key, jcr->catalog->db_ssl_cert,
+                jcr->catalog->db_ssl_ca, jcr->catalog->db_ssl_capath, 
+                jcr->catalog->db_ssl_cipher,
+                jcr->catalog->mult_db_connections,
+                jcr->catalog->disable_batch_insert);
    if (!jcr->db || !db_open_database(jcr, jcr->db)) {
       Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
                  jcr->catalog->db_name);
       if (jcr->db) {
          Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
          db_close_database(jcr, jcr->db);
+         jcr->db = NULL;
       }
       goto bail_out;
    }
+
    Dmsg0(150, "DB opened\n");
    if (!jcr->fname) {
       jcr->fname = get_pool_memory(PM_FNAME);
@@ -232,6 +239,148 @@ bail_out:
    return false;
 }
 
+/*
+ * Setup a job for a resume command
+ */
+static bool setup_resume_job(JCR *jcr, JOB_DBR *jr)
+{
+   int errstat;
+   jcr->lock();
+   Dsm_check(100);
+   init_msg(jcr, jcr->messages);
+
+   /* Initialize termination condition variable */
+   if ((errstat = pthread_cond_init(&jcr->term_wait, NULL)) != 0) {
+      berrno be;
+      Jmsg1(jcr, M_FATAL, 0, _("Unable to init job cond variable: ERR=%s\n"), be.bstrerror(errstat));
+      jcr->unlock();
+      goto bail_out;
+   }
+   jcr->term_wait_inited = true;
+
+   jcr->setJobStatus(JS_Created);
+   jcr->unlock();
+
+   /*
+    * Open database
+    */
+   Dmsg0(100, "Open database\n");
+   jcr->db = db_init_database(jcr, jcr->catalog->db_driver, jcr->catalog->db_name,
+                              jcr->catalog->db_user, jcr->catalog->db_password,
+                              jcr->catalog->db_address, jcr->catalog->db_port,
+                              jcr->catalog->db_socket, jcr->catalog->db_ssl_mode,
+                              jcr->catalog->db_ssl_key, jcr->catalog->db_ssl_cert,
+                              jcr->catalog->db_ssl_ca, jcr->catalog->db_ssl_capath, 
+                              jcr->catalog->db_ssl_cipher,
+                              jcr->catalog->mult_db_connections,
+                              jcr->catalog->disable_batch_insert);
+   if (!jcr->db || !db_open_database(jcr, jcr->db)) {
+      Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
+                 jcr->catalog->db_name);
+      if (jcr->db) {
+         Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
+         db_close_database(jcr, jcr->db);
+         jcr->db = NULL;
+      }
+      goto bail_out;
+   }
+   Dmsg0(100, "DB opened\n");
+   if (!jcr->fname) {
+      jcr->fname = get_pool_memory(PM_FNAME);
+   }
+   if (!jcr->pool_source) {
+      jcr->pool_source = get_pool_memory(PM_MESSAGE);
+      pm_strcpy(jcr->pool_source, _("unknown source"));
+   }
+   if (!jcr->next_pool_source) {
+      jcr->next_pool_source = get_pool_memory(PM_MESSAGE);
+      pm_strcpy(jcr->next_pool_source, _("unknown source"));
+   }
+
+
+   /*
+    * Setup Job record.  Make sure original job is Incomplete.
+    */
+   memcpy(&jcr->jr, jr, sizeof(JOB_DBR));
+   jcr->sched_time = jcr->jr.SchedTime;
+   jcr->start_time = jcr->jr.StartTime;
+   jcr->jr.EndTime = 0;               /* perhaps rescheduled, clear it */
+   jcr->setJobType(jcr->jr.JobType);
+   jcr->setJobLevel(jcr->jr.JobLevel);
+   jcr->JobId = jcr->jr.JobId;
+   if (!get_or_create_client_record(jcr)) {
+      Dmsg0(100, "Could not create client record.\n");
+      goto bail_out;
+   }
+
+   Dmsg6(100, "Got job record JobId=%d Job=%s Name=%s Type=%c Level=%c Status=%c\n",
+       jcr->jr.JobId, jcr->jr.Job, jcr->jr.Name, jcr->jr.JobType, jcr->jr.JobLevel,
+       jcr->jr.JobStatus);
+   if (jcr->jr.JobStatus != JS_Incomplete) {
+      /* ***FIXME*** add error message */
+      Dmsg1(100, "Job is not an Incomplete: status=%c\n", jcr->jr.JobStatus);
+      goto bail_out;
+   }
+   bstrncpy(jcr->Job, jcr->jr.Job, sizeof(jcr->Job));
+   jcr->setJobType(jcr->jr.JobType);
+   jcr->setJobLevel(jcr->jr.JobLevel);
+
+   generate_daemon_event(jcr, "JobStart");
+   new_plugins(jcr);                  /* instantiate plugins for this jcr */
+   generate_plugin_event(jcr, bDirEventJobStart);
+
+   if (job_canceled(jcr)) {
+      Dmsg0(100, "Oops. Job canceled\n");
+      goto bail_out;
+   }
+
+   /* Re-run the old job */
+   jcr->rerunning = true;
+
+   /*
+    * Now, do pre-run stuff, like setting job level (Inc/diff, ...)
+    *  this allows us to setup a proper job start record for restarting
+    *  in case of later errors.
+    */
+   switch (jcr->getJobType()) {
+   case JT_BACKUP:
+      if (!do_backup_init(jcr)) {
+         backup_cleanup(jcr, JS_ErrorTerminated);
+         goto bail_out;
+      }
+      break;
+   default:
+      Pmsg1(0, _("Unimplemented job type: %d\n"), jcr->getJobType());
+      jcr->setJobStatus(JS_ErrorTerminated);
+      goto bail_out;
+   }
+
+   generate_plugin_event(jcr, bDirEventJobInit);
+   Dsm_check(100);
+   return true;
+
+bail_out:
+   return false;
+}
+
+JobId_t resume_job(JCR *jcr, JOB_DBR *jr)
+{
+   int stat;
+   if (setup_resume_job(jcr, jr)) {
+      Dmsg0(200, "Add jrc to work queue\n");
+      /* Queue the job to be run */
+      if ((stat = jobq_add(&job_queue, jcr)) != 0) {
+         berrno be;
+         Jmsg(jcr, M_FATAL, 0, _("Could not add job queue: ERR=%s\n"), be.bstrerror(stat));
+         return 0;
+      }
+      return jcr->JobId;
+   }
+   return 0;
+}
+
+
+
 void update_job_end(JCR *jcr, int TermCode)
 {
    dequeue_messages(jcr);             /* display any queued messages */
@@ -367,18 +516,23 @@ void sd_msg_thread_send_signal(JCR *jcr, int sig)
    jcr->unlock();
 }
 
-static int cancel_file_daemon_job(UAContext *ua, const char *cmd, JCR *jcr)
+static bool cancel_file_daemon_job(UAContext *ua, const char *cmd, JCR *jcr)
 {
+   CLIENT *old_client;
+
    if (!jcr->client) {
       Dmsg0(100, "No client to cancel\n");
-      return 0;
+      return false;
    }
+   old_client = ua->jcr->client;
    ua->jcr->client = jcr->client;
    if (!connect_to_file_daemon(ua->jcr, 10, FDConnectTimeout, 1)) {
       ua->error_msg(_("Failed to connect to File daemon.\n"));
-      return 0;
+      ua->jcr->client = old_client;
+      return false;
    }
-   Dmsg0(100, "Connected to file daemon\n");
+   Dmsg3(10, "Connected to file daemon %s for cancel ua.jcr=%p jcr=%p\n",
+         ua->jcr->client->name(), ua->jcr, jcr);
    BSOCK *fd = ua->jcr->file_bsock;
    fd->fsend("%s Job=%s\n", cmd, jcr->Job);
    while (fd->recv() >= 0) {
@@ -386,8 +540,8 @@ static int cancel_file_daemon_job(UAContext *ua, const char *cmd, JCR *jcr)
    }
    fd->signal(BNET_TERMINATE);
    free_bsock(ua->jcr->file_bsock);
-   ua->jcr->client = NULL;
-   return 1;
+   ua->jcr->client = old_client;
+   return true;
 }
 
 static bool cancel_sd_job(UAContext *ua, const char *cmd, JCR *jcr)
@@ -417,7 +571,10 @@ static bool cancel_sd_job(UAContext *ua, const char *cmd, JCR *jcr)
       ua->error_msg(_("Failed to connect to Storage daemon.\n"));
       return false;
    }
-   Dmsg0(200, "Connected to storage daemon\n");
+
+   Dmsg3(10, "Connected to storage daemon %s for cancel ua.jcr=%p jcr=%p\n",
+         ua->jcr->wstore->name(), ua->jcr, jcr);
+
    BSOCK *sd = ua->jcr->store_bsock;
    sd->fsend("%s Job=%s\n", cmd, jcr->Job);
    while (sd->recv() >= 0) {
@@ -431,41 +588,64 @@ static bool cancel_sd_job(UAContext *ua, const char *cmd, JCR *jcr)
 /* The FD is not connected, so we try to complete JCR fields and send
  * the cancel command.
  */
-static int cancel_inactive_job(UAContext *ua, JCR *jcr)
+int cancel_inactive_job(UAContext *ua)
 {
    CLIENT_DBR cr;
    JOB_DBR    jr;
    int        i;
    USTORE     store;
+   CLIENT    *client;
+   JCR       *jcr = new_jcr(sizeof(JCR), dird_free_jcr);
 
-   if (!jcr->client) {
-      memset(&cr, 0, sizeof(cr));
+   memset(&jr, 0, sizeof(jr));
+   memset(&cr, 0, sizeof(cr));
 
-      /* User is kind enough to provide the client name */
-      if ((i = find_arg_with_value(ua, "client")) > 0) {
-         bstrncpy(cr.Name, ua->argv[i], sizeof(cr.Name));
+   if ((i = find_arg_with_value(ua, "jobid")) > 0) {
+      jr.JobId = str_to_int64(ua->argv[i]);
 
-      } else {
-         memset(&jr, 0, sizeof(jr));
-         bstrncpy(jr.Job, jcr->Job, sizeof(jr.Job));
+   } else if ((i = find_arg_with_value(ua, "ujobid")) > 0) {
+      bstrncpy(jr.Job, ua->argv[i], sizeof(jr.Job));
 
-         if (!open_client_db(ua)) {
-            goto bail_out;
-         }
-         if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
-            goto bail_out;
-         }
-         cr.ClientId = jr.ClientId;
-         if (!cr.ClientId || !db_get_client_record(ua->jcr, ua->db, &cr)) {
-            goto bail_out;
-         }
-      }
+   } else {
+      ua->error_msg(_("jobid/ujobid argument not found.\n"));
+      goto bail_out;
+   }
+
+   if (!open_client_db(ua)) {
+      goto bail_out;
+   }
 
-      if (acl_access_ok(ua, Client_ACL, cr.Name)) {
-         jcr->client = (CLIENT *)GetResWithName(R_CLIENT, cr.Name);
+   if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
+      ua->error_msg(_("Job %ld/%s not found in database.\n"), jr.JobId, jr.Job);
+      goto bail_out;
+   }
+
+   if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
+      ua->error_msg(_("Job %s is not accessible from this console\n"), jr.Name);
+      goto bail_out;
+   }
+
+   cr.ClientId = jr.ClientId;
+   if (!cr.ClientId || !db_get_client_record(ua->jcr, ua->db, &cr)) {
+      ua->error_msg(_("Client %ld not found in database.\n"), jr.ClientId);
+      goto bail_out;
+   }
+
+   if (acl_access_client_ok(ua, cr.Name, jr.JobType)) {
+      client = (CLIENT *)GetResWithName(R_CLIENT, cr.Name);
+      if (client) {
+         jcr->client = client;
+      } else {
+         Jmsg1(jcr, M_FATAL, 0, _("Client resource \"%s\" does not exist.\n"), cr.Name);
+         goto bail_out;
       }
+   } else {
+      goto bail_out;
    }
 
+   jcr->JobId = jr.JobId;
+   bstrncpy(jcr->Job, jr.Job, sizeof(jcr->Job));
+
    cancel_file_daemon_job(ua, "cancel", jcr);
 
    /* At this time, we can't really guess the storage name from
@@ -476,11 +656,12 @@ static int cancel_inactive_job(UAContext *ua, JCR *jcr)
       goto bail_out;
    }
 
-   set_wstorage(ua->jcr, &store);
-
+   set_wstorage(jcr, &store);
    cancel_sd_job(ua, "cancel", jcr);
 
 bail_out:
+   jcr->JobId = 0;
+   free_jcr(jcr);
    return 1;
 }
 
@@ -491,26 +672,32 @@ bail_out:
  *  Returns: true  if cancel appears to be successful
  *           false on failure. Message sent to ua->jcr.
  */
-bool cancel_job(UAContext *ua, JCR *jcr, bool cancel)
+bool
+cancel_job(UAContext *ua, JCR *jcr, int wait,  bool cancel)
 {
    char ed1[50];
    int32_t old_status = jcr->JobStatus;
    int status;
    const char *reason, *cmd;
-   bool force = find_arg(ua, "inactive") > 0;
-   JCR *wjcr = jcr->wjcr;
-
 
-   /* If the user explicitely ask, we can send the cancel command to
-    * the FD.
-    */
-   if (cancel && force) {
-      return cancel_inactive_job(ua, jcr);
+   if (!cancel) {               /* stop the job */
+      if (!jcr->can_be_stopped()) {
+         ua->error_msg(_("Cannot stop JobId %s, Job %s is not a regular Backup Job\n"),
+                       edit_uint64(jcr->JobId, ed1), jcr->Job);
+         return true;
+      }
+   }
+   
+   if (cancel) {
+      status = JS_Canceled;
+      reason = _("canceled");
+      cmd = NT_("cancel");
+   } else {
+      status = JS_Incomplete;
+      reason = _("stopped");
+      cmd = NT_("stop");
+      jcr->RescheduleIncompleteJobs = false; /* do not restart */
    }
-
-   status = JS_Canceled;
-   reason = _("canceled");
-   cmd = NT_("cancel");
 
    jcr->setJobStatus(status);
 
@@ -533,8 +720,11 @@ bool cancel_job(UAContext *ua, JCR *jcr, bool cancel)
 
       /* Cancel File daemon */
       if (jcr->file_bsock) {
+         btimer_t *tid;
          /* do not return now, we want to try to cancel the sd */
+         tid = start_bsock_timer(jcr->file_bsock, 120);
          cancel_file_daemon_job(ua, cmd, jcr);
+         stop_bsock_timer(tid);
       }
 
       /* We test file_bsock because the previous operation can take
@@ -547,8 +737,11 @@ bool cancel_job(UAContext *ua, JCR *jcr, bool cancel)
 
       /* Cancel Storage daemon */
       if (jcr->store_bsock) {
+         btimer_t *tid;
          /* do not return now, we want to try to cancel the sd socket */
+         tid = start_bsock_timer(jcr->store_bsock, 120);
          cancel_sd_job(ua, cmd, jcr);
+         stop_bsock_timer(tid);
       }
 
       /* We test file_bsock because the previous operation can take
@@ -562,11 +755,18 @@ bool cancel_job(UAContext *ua, JCR *jcr, bool cancel)
       }
 
       /* Cancel Copy/Migration Storage daemon */
-      if (wjcr  && wjcr->store_bsock) {
-         /* do not return now, we want to try to cancel the sd socket */
-         cancel_sd_job(ua, cmd, wjcr);
-
-         /* We test file_bsock because the previous operation can take
+      if (jcr->wjcr) {
+         /* The wjcr is valid until we call free_jcr(jcr) */
+         JCR *wjcr = jcr->wjcr;
+
+         if (wjcr->store_bsock) {
+             btimer_t *tid;
+            /* do not return now, we want to try to cancel the sd socket */
+            tid = start_bsock_timer(wjcr->store_bsock, 120);
+            cancel_sd_job(ua, cmd, wjcr);
+            stop_bsock_timer(tid);
+         }
+         /* We test store_bsock because the previous operation can take
           * several minutes
           */
          if (wjcr->store_bsock && cancel) {
@@ -638,14 +838,33 @@ static void job_monitor_destructor(watchdog_t *self)
    free_jcr(control_jcr);
 }
 
-static void job_monitor_watchdog(watchdog_t *self)
+extern "C" void *cancel_thread(void *arg)
 {
-   JCR *control_jcr, *jcr;
+   JCR *jcr = (JCR *)arg;
+   UAContext *ua;
+   JCR *control_jcr;
 
-   control_jcr = (JCR *)self->data;
+   pthread_detach(pthread_self());
+   ua = new_ua_context(jcr);
+   control_jcr = new_control_jcr("*CancelThread*", JT_SYSTEM);
+   ua->jcr = control_jcr;
+
+   Dmsg3(400, "Cancelling JCR %p JobId=%d (%s)\n", jcr, jcr->JobId, jcr->Job);
+   cancel_job(ua, jcr, 120);
+   Dmsg2(400, "Have cancelled JCR %p JobId=%d\n", jcr, jcr->JobId);
+
+   free_ua_context(ua);
+   free_jcr(control_jcr);
+   free_jcr(jcr);
+   return NULL;
+}
+
+static void job_monitor_watchdog(watchdog_t *wd)
+{
+   JCR *jcr;
 
    Dsm_check(100);
-   Dmsg1(800, "job_monitor_watchdog %p called\n", self);
+   Dmsg1(800, "job_monitor_watchdog %p called\n", wd);
 
    foreach_jcr(jcr) {
       bool cancel = false;
@@ -673,14 +892,15 @@ static void job_monitor_watchdog(watchdog_t *self)
       }
 
       if (cancel) {
-         Dmsg3(800, "Cancelling JCR %p jobid %d (%s)\n", jcr, jcr->JobId, jcr->Job);
-         UAContext *ua = new_ua_context(jcr);
-         ua->jcr = control_jcr;
-         cancel_job(ua, jcr);
-         free_ua_context(ua);
-         Dmsg2(800, "Have cancelled JCR %p Job=%d\n", jcr, jcr->JobId);
+         pthread_t thid;
+         int status;
+         jcr->inc_use_count();
+         if ((status=pthread_create(&thid, NULL, cancel_thread, (void *)jcr)) != 0) {
+            berrno be;
+            Jmsg1(jcr, M_WARNING, 0, _("Cannot create cancel thread: ERR=%s\n"), be.bstrerror(status));
+            free_jcr(jcr);
+         }
       }
-
    }
    /* Keep reference counts correct */
    endeach_jcr(jcr);
@@ -790,7 +1010,7 @@ DBId_t get_or_create_pool_record(JCR *jcr, char *pool_name)
    while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
       /* Try to create the pool */
       if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
-         Jmsg(jcr, M_FATAL, 0, _("Pool \"%s\" not in database. ERR=%s"), pr.Name,
+         Jmsg(jcr, M_FATAL, 0, _("Cannot create pool \"%s\" in database. ERR=%s"), pr.Name,
             db_strerror(jcr->db));
          return 0;
       } else {
@@ -809,38 +1029,32 @@ bool allow_duplicate_job(JCR *jcr)
 {
    JOB *job = jcr->job;
    JCR *djcr;                /* possible duplicate job */
-   bool cancel_dup = false;
-   bool cancel_me = false;
 
-   /*
-    * See if AllowDuplicateJobs is set or
-    * if duplicate checking is disabled for this job.
-    */
+   /* Is AllowDuplicateJobs is set or is duplicate checking
+    *  disabled for this job? */
    if (job->AllowDuplicateJobs || jcr->IgnoreDuplicateJobChecking) {
       return true;
    }
-
    Dmsg0(800, "Enter allow_duplicate_job\n");
-
    /*
     * After this point, we do not want to allow any duplicate
     * job to run.
     */
 
    foreach_jcr(djcr) {
-      if (jcr == djcr || djcr->JobId == 0) {
+      if (jcr == djcr || djcr->is_internal_job() || !djcr->job) {
          continue;                   /* do not cancel this job or consoles */
       }
-
-      /*
-       * See if this Job has the IgnoreDuplicateJobChecking flag set, ignore it
-       * for any checking against other jobs.
-       */
+      /* Does Job has the IgnoreDuplicateJobChecking flag set,
+       * if so do not check it against other jobs */
       if (djcr->IgnoreDuplicateJobChecking) {
          continue;
       }
-
-      if (strcmp(job->name(), djcr->job->name()) == 0) {
+      if ((strcmp(job->name(), djcr->job->name()) == 0) &&
+          djcr->getJobType() == jcr->getJobType()) /* A duplicate is about the same name and the same type */
+      {
+         bool cancel_dup = false;
+         bool cancel_me = false;
          if (job->DuplicateJobProximity > 0) {
             utime_t now = (utime_t)time(NULL);
             if ((now - djcr->start_time) > job->DuplicateJobProximity) {
@@ -851,6 +1065,7 @@ bool allow_duplicate_job(JCR *jcr)
              djcr->getJobType() == 'B' && jcr->getJobType() == 'B') {
             switch (jcr->getJobLevel()) {
             case L_FULL:
+            case L_VIRTUAL_FULL:
                if (djcr->getJobLevel() == L_DIFFERENTIAL ||
                    djcr->getJobLevel() == L_INCREMENTAL) {
                   cancel_dup = true;
@@ -881,11 +1096,8 @@ bool allow_duplicate_job(JCR *jcr)
               break;     /* get out of foreach_jcr */
             }
          }
-
-         /*
-          * Cancel one of the two jobs (me or dup)
-          * If CancelQueuedDuplicates is set do so only if job is queued.
-          */
+         /* Cancel one of the two jobs (me or dup) */
+         /* If CancelQueuedDuplicates is set do so only if job is queued */
          if (job->CancelQueuedDuplicates) {
              switch (djcr->JobStatus) {
              case JS_Created:
@@ -902,23 +1114,18 @@ bool allow_duplicate_job(JCR *jcr)
                 break;
              }
          }
-
          if (cancel_dup || job->CancelRunningDuplicates) {
-            /*
-             * Zap the duplicated job djcr
-             */
+            /* Zap the duplicated job djcr */
             UAContext *ua = new_ua_context(jcr);
             Jmsg(jcr, M_INFO, 0, _("Cancelling duplicate JobId=%d.\n"), djcr->JobId);
-            cancel_job(ua, djcr);
+            cancel_job(ua, djcr, 60);
             bmicrosleep(0, 500000);
             djcr->setJobStatus(JS_Canceled);
-            cancel_job(ua, djcr);
+            cancel_job(ua, djcr, 60);
             free_ua_context(ua);
             Dmsg2(800, "Cancel dup %p JobId=%d\n", djcr, djcr->JobId);
          } else {
-            /*
-             * Zap current job
-             */
+             /* Zap current job */
             jcr->setJobStatus(JS_Canceled);
             Jmsg(jcr, M_FATAL, 0, _("JobId %d already running. Duplicate job not allowed.\n"),
                djcr->JobId);
@@ -942,16 +1149,19 @@ bool apply_wstorage_overrides(JCR *jcr, POOL *opool)
    const char *source;
 
    Dmsg1(100, "Original pool=%s\n", opool->name());
-   if (jcr->run_next_pool_override) {
+   if (jcr->cmdline_next_pool_override) {
+      /* Can be Command line or User input */
+      source = NPRT(jcr->next_pool_source);
+   } else if (jcr->run_next_pool_override) {
       pm_strcpy(jcr->next_pool_source, _("Run NextPool override"));
       pm_strcpy(jcr->pool_source, _("Run NextPool override"));
-      source = _("Storage from Run NextPool override");
+      source = _("Run NextPool override");
    } else if (jcr->job->next_pool) {
       /* Use Job Next Pool */
       jcr->next_pool = jcr->job->next_pool;
       pm_strcpy(jcr->next_pool_source, _("Job's NextPool resource"));
       pm_strcpy(jcr->pool_source, _("Job's NextPool resource"));
-      source = _("Storage from Job's NextPool resource");
+      source = _("Job's NextPool resource");
    } else {
       /* Default to original pool->NextPool */
       jcr->next_pool = opool->NextPool;
@@ -961,7 +1171,7 @@ bool apply_wstorage_overrides(JCR *jcr, POOL *opool)
       }
       pm_strcpy(jcr->next_pool_source, _("Job Pool's NextPool resource"));
       pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
-      source = _("Storage from Pool's NextPool resource");
+      source = _("Pool's NextPool resource");
    }
 
    /*
@@ -1009,6 +1219,17 @@ void apply_pool_overrides(JCR *jcr)
          }
       }
       break;
+      case L_VIRTUAL_FULL:
+         if (jcr->vfull_pool) {
+            jcr->pool = jcr->vfull_pool;
+            pool_override = true;
+            if (jcr->run_vfull_pool_override) {
+               pm_strcpy(jcr->pool_source, _("Run VFullPool override"));
+            } else {
+               pm_strcpy(jcr->pool_source, _("Job VFullPool override"));
+            }
+         }
+         break;
    case L_INCREMENTAL:
       if (jcr->inc_pool) {
          jcr->pool = jcr->inc_pool;
@@ -1047,6 +1268,10 @@ bool get_or_create_client_record(JCR *jcr)
 {
    CLIENT_DBR cr;
 
+   if (!jcr->client) {
+      Jmsg(jcr, M_FATAL, 0, _("No Client specified.\n"));
+      return false;
+   }
    memset(&cr, 0, sizeof(cr));
    bstrncpy(cr.Name, jcr->client->hdr.name, sizeof(cr.Name));
    cr.AutoPrune = jcr->client->AutoPrune;
@@ -1138,7 +1363,7 @@ void update_job_end_record(JCR *jcr)
    jcr->jr.ReadBytes = jcr->ReadBytes;
    jcr->jr.VolSessionId = jcr->VolSessionId;
    jcr->jr.VolSessionTime = jcr->VolSessionTime;
-   jcr->jr.JobErrors = jcr->JobErrors;
+   jcr->jr.JobErrors = jcr->JobErrors + jcr->SDErrors;
    jcr->jr.HasBase = jcr->HasBase;
    if (!db_update_job_end_record(jcr, jcr->db, &jcr->jr)) {
       Jmsg(jcr, M_WARNING, 0, _("Error updating job record. %s"),
@@ -1169,6 +1394,7 @@ void create_unique_job_name(JCR *jcr, const char *base_name)
    char name[MAX_NAME_LENGTH];
    char *p;
    int len;
+   int local_seq;
 
    /* Guarantee unique start time -- maximum one per second, and
     * thus unique Job Name
@@ -1183,6 +1409,7 @@ void create_unique_job_name(JCR *jcr, const char *base_name)
       }
    }
    last_start_time = now;
+   local_seq = seq;
    V(mutex);                          /* allow creation of jobs */
    jcr->start_time = now;
    /* Form Unique JobName */
@@ -1192,7 +1419,7 @@ void create_unique_job_name(JCR *jcr, const char *base_name)
    len = strlen(dt) + 5;   /* dt + .%02d EOS */
    bstrncpy(name, base_name, sizeof(name));
    name[sizeof(name)-len] = 0;          /* truncate if too long */
-   bsnprintf(jcr->Job, sizeof(jcr->Job), "%s.%s_%02d", name, dt, seq); /* add date & time */
+   bsnprintf(jcr->Job, sizeof(jcr->Job), "%s.%s_%02d", name, dt, local_seq); /* add date & time */
    /* Convert spaces into underscores */
    for (p=jcr->Job; *p; p++) {
       if (*p == ' ') {
@@ -1238,6 +1465,14 @@ void dird_free_jcr(JCR *jcr)
    Dmsg0(200, "Start dird free_jcr\n");
 
    dird_free_jcr_pointers(jcr);
+   if (jcr->bsr_list) {
+      free_bsr(jcr->bsr_list);
+      jcr->bsr_list = NULL;
+   }
+   if (jcr->wjcr) {
+      free_jcr(jcr->wjcr);
+      jcr->wjcr = NULL;
+   }
    /* Free bsock packets */
    free_bsock(jcr->file_bsock);
    free_bsock(jcr->store_bsock);
@@ -1263,18 +1498,26 @@ void dird_free_jcr(JCR *jcr)
    free_and_null_pool_memory(jcr->rpool_source);
    free_and_null_pool_memory(jcr->wstore_source);
    free_and_null_pool_memory(jcr->rstore_source);
+   free_and_null_pool_memory(jcr->next_vol_list);
+   free_and_null_pool_memory(jcr->component_fname);
 
    /* Delete lists setup to hold storage pointers */
    free_rwstorage(jcr);
 
    jcr->job_end_push.destroy();
 
-   if (jcr->JobId != 0) {
+   if (jcr->JobId != 0)
       write_state_file(director->working_directory, "bacula-dir", get_first_port_host_order(director->DIRaddrs));
-   }
 
+   if (jcr->plugin_config) {
+      free_plugin_config_items(jcr->plugin_config);
+      delete jcr->plugin_config;
+      jcr->plugin_config = NULL;
+   }
    free_plugins(jcr);                 /* release instantiated plugins */
 
+   garbage_collect_memory_pool();
+
    Dmsg0(200, "End dird free_jcr\n");
 }
 
@@ -1325,7 +1568,9 @@ void set_jcr_defaults(JCR *jcr, JOB *job)
       jcr->setJobLevel(job->JobLevel);
       break;
    }
-
+   if (!jcr->next_vol_list) {
+      jcr->next_vol_list = get_pool_memory(PM_FNAME);
+   }
    if (!jcr->fname) {
       jcr->fname = get_pool_memory(PM_FNAME);
    }
@@ -1347,15 +1592,24 @@ void set_jcr_defaults(JCR *jcr, JOB *job)
       copy_rwstorage(jcr, job->pool->storage, _("Pool resource"));
    }
    jcr->client = job->client;
+   ASSERT2(jcr->client, "jcr->client==NULL!!!");
    if (!jcr->client_name) {
       jcr->client_name = get_pool_memory(PM_NAME);
    }
    pm_strcpy(jcr->client_name, jcr->client->name());
    jcr->pool = job->pool;
    pm_strcpy(jcr->pool_source, _("Job resource"));
-   jcr->next_pool = job->pool->NextPool;
-   pm_strcpy(jcr->next_pool_source, _("Job's NextPool resource"));
+   if (job->next_pool) {
+      /* Use Job's Next Pool */
+      jcr->next_pool = job->next_pool;
+      pm_strcpy(jcr->next_pool_source, _("Job's NextPool resource"));
+   } else {
+      /* Default to original pool->NextPool */
+      jcr->next_pool = job->pool->NextPool;
+      pm_strcpy(jcr->next_pool_source, _("Job Pool's NextPool resource"));
+   }
    jcr->full_pool = job->full_pool;
+   jcr->vfull_pool = job->vfull_pool;
    jcr->inc_pool = job->inc_pool;
    jcr->diff_pool = job->diff_pool;
    if (job->pool->catalog) {
@@ -1371,13 +1625,9 @@ void set_jcr_defaults(JCR *jcr, JOB *job)
    jcr->spool_data = job->spool_data;
    jcr->spool_size = job->spool_size;
    jcr->write_part_after_job = job->write_part_after_job;
-   jcr->IgnoreDuplicateJobChecking = job->IgnoreDuplicateJobChecking;
    jcr->MaxRunSchedTime = job->MaxRunSchedTime;
-   if (jcr->RestoreBootstrap) {
-      free(jcr->RestoreBootstrap);
-      jcr->RestoreBootstrap = NULL;
-   }
    /* This can be overridden by Console program */
+   bfree_and_null(jcr->RestoreBootstrap);
    if (job->RestoreBootstrap) {
       jcr->RestoreBootstrap = bstrdup(job->RestoreBootstrap);
    }
@@ -1592,45 +1842,55 @@ void create_clones(JCR *jcr)
 }
 
 /*
- * Given: a JobId in jcr->previous_jr.JobId,
+ * Given: a JobId  and FileIndex
  *  this subroutine writes a bsr file to restore that job.
  * Returns: -1 on error
  *           number of files if OK
  */
-int create_restore_bootstrap_file(JCR *jcr)
+int create_restore_bootstrap_file(JCR *jcr, JobId_t jobid, int findex1, int findex2)
 {
    RESTORE_CTX rx;
    UAContext *ua;
    int files;
 
    memset(&rx, 0, sizeof(rx));
-   rx.bsr = new_bsr();
    rx.JobIds = (char *)"";
-   rx.bsr->JobId = jcr->previous_jr.JobId;
+
+   rx.bsr_list = create_bsr_list(jobid, findex1, findex2);
+
    ua = new_ua_context(jcr);
-   if (!complete_bsr(ua, rx.bsr)) {
+   if (!complete_bsr(ua, rx.bsr_list)) {
       files = -1;
       goto bail_out;
    }
-   rx.bsr->fi = new_findex();
-   rx.bsr->fi->findex = 1;
-   rx.bsr->fi->findex2 = jcr->previous_jr.JobFiles;
+
    jcr->ExpectedFiles = write_bsr_file(ua, rx);
    if (jcr->ExpectedFiles == 0) {
       files = 0;
       goto bail_out;
    }
    free_ua_context(ua);
-   free_bsr(rx.bsr);
+   free_bsr(rx.bsr_list);
    jcr->needs_sd = true;
    return jcr->ExpectedFiles;
 
 bail_out:
    free_ua_context(ua);
-   free_bsr(rx.bsr);
+   free_bsr(rx.bsr_list);
    return files;
 }
 
+/*
+ * Given: a JobId in jcr->previous_jr.JobId,
+ *  this subroutine writes a bsr file to restore that job.
+ * Returns: -1 on error
+ *           number of files if OK
+ */
+int create_restore_bootstrap_file(JCR *jcr)
+{
+   return create_restore_bootstrap_file(jcr, jcr->previous_jr.JobId, 1, jcr->previous_jr.JobFiles);
+}
+
 /* TODO: redirect command ouput to job log */
 bool run_console_command(JCR *jcr, const char *cmd)
 {
@@ -1648,6 +1908,7 @@ bool run_console_command(JCR *jcr, const char *cmd)
    } else {
      ok = do_a_command(ua);
    }
+   close_db(ua);
    free_ua_context(ua);
    free_jcr(ljcr);
    return ok;