]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/dird/job.c
Tweak copyrights again
[bacula/bacula] / bacula / src / dird / job.c
index 15beadf27350f440a49f48ba392fcd425b6e4813..768b6cf1cfe34924ffca573c9ec4b50c18fd29ce 100644 (file)
@@ -1,12 +1,12 @@
 /*
    Bacula® - The Network Backup Solution
 
-   Copyright (C) 2000-2008 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.
 
    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.
 
-   Bacula® is a registered trademark of John Walker.
+   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.
@@ -31,7 +31,6 @@
  *
  *     Kern Sibbald, October MM
  *
- *    Version $Id$
  */
 
 #include "bacula.h"
@@ -41,8 +40,9 @@
 static void *job_thread(void *arg);
 static void job_monitor_watchdog(watchdog_t *self);
 static void job_monitor_destructor(watchdog_t *self);
-static bool job_check_maxwaittime(JCR *control_jcr, JCR *jcr);
-static bool job_check_maxruntime(JCR *control_jcr, JCR *jcr);
+static bool job_check_maxwaittime(JCR *jcr);
+static bool job_check_maxruntime(JCR *jcr);
+static bool job_check_maxrunschedtime(JCR *jcr);
 
 /* Imported subroutines */
 extern void term_scheduler();
@@ -104,30 +104,31 @@ bool setup_job(JCR *jcr)
    int errstat;
 
    jcr->lock();
-   sm_check(__FILE__, __LINE__, true);
+   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;
 
    create_unique_job_name(jcr, jcr->job->name());
-   set_jcr_job_status(jcr, JS_Created);
+   jcr->setJobStatus(JS_Created);
    jcr->unlock();
 
    /*
     * Open database
     */
    Dmsg0(100, "Open database\n");
-   jcr->db=db_init(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->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);
    if (!jcr->db || !db_open_database(jcr, jcr->db)) {
       Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
                  jcr->catalog->db_name);
@@ -138,7 +139,6 @@ bool setup_job(JCR *jcr)
       goto bail_out;
    }
    Dmsg0(150, "DB opened\n");
-
    if (!jcr->fname) {
       jcr->fname = get_pool_memory(PM_FNAME);
    }
@@ -146,8 +146,8 @@ bool setup_job(JCR *jcr)
       jcr->pool_source = get_pool_memory(PM_MESSAGE);
       pm_strcpy(jcr->pool_source, _("unknown source"));
    }
-   Dmsg2(500, "pool=%s (From %s)\n", jcr->pool->name(), jcr->pool_source);
-   if (jcr->JobType == JT_MIGRATE) {
+
+   if (jcr->JobReads()) {
       if (!jcr->rpool_source) {
          jcr->rpool_source = get_pool_memory(PM_MESSAGE);
          pm_strcpy(jcr->rpool_source, _("unknown source"));
@@ -171,50 +171,70 @@ bool setup_job(JCR *jcr)
        jcr->JobId, jcr->Job, jcr->jr.JobType, 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)) {
       goto bail_out;
    }
 
+   if (jcr->JobReads() && !jcr->rstorage) {
+      if (jcr->job->storage) {
+         copy_rwstorage(jcr, jcr->job->storage, _("Job resource"));
+      } else {
+         copy_rwstorage(jcr, jcr->job->pool->storage, _("Pool resource"));
+      }
+   }
+   if (!jcr->JobReads()) {
+      free_rstorage(jcr);
+   }
+
    /*
     * 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->JobType) {
+   switch (jcr->getJobType()) {
    case JT_BACKUP:
       if (!do_backup_init(jcr)) {
          backup_cleanup(jcr, JS_ErrorTerminated);
+         goto bail_out;
       }
       break;
    case JT_VERIFY:
       if (!do_verify_init(jcr)) {
          verify_cleanup(jcr, JS_ErrorTerminated);
+         goto bail_out;
       }
       break;
    case JT_RESTORE:
       if (!do_restore_init(jcr)) {
          restore_cleanup(jcr, JS_ErrorTerminated);
+         goto bail_out;
       }
       break;
    case JT_ADMIN:
       if (!do_admin_init(jcr)) {
          admin_cleanup(jcr, JS_ErrorTerminated);
+         goto bail_out;
       }
       break;
+   case JT_COPY:
    case JT_MIGRATE:
       if (!do_migration_init(jcr)) { 
          migration_cleanup(jcr, JS_ErrorTerminated);
+         goto bail_out;
       }
       break;
    default:
-      Pmsg1(0, _("Unimplemented job type: %d\n"), jcr->JobType);
-      set_jcr_job_status(jcr, JS_ErrorTerminated);
-      break;
+      Pmsg1(0, _("Unimplemented job type: %d\n"), jcr->getJobType());
+      jcr->setJobStatus(JS_ErrorTerminated);
+      goto bail_out;
    }
 
    generate_job_event(jcr, "JobInit");
-   Dsm_check(1);
+   generate_plugin_event(jcr, bDirEventJobInit);
+   Dsm_check(100);
    return true;
 
 bail_out:
@@ -224,7 +244,7 @@ bail_out:
 void update_job_end(JCR *jcr, int TermCode)
 {
    dequeue_messages(jcr);             /* display any queued messages */
-   set_jcr_job_status(jcr, TermCode);
+   jcr->setJobStatus(TermCode);
    update_job_end_record(jcr);
 }
 
@@ -239,19 +259,24 @@ static void *job_thread(void *arg)
    JCR *jcr = (JCR *)arg;
 
    pthread_detach(pthread_self());
-   Dsm_check(1);
+   Dsm_check(100);
 
    Dmsg0(200, "=====Start Job=========\n");
-   set_jcr_job_status(jcr, JS_Running);   /* this will be set only if no error */
+   jcr->setJobStatus(JS_Running);   /* this will be set only if no error */
    jcr->start_time = time(NULL);      /* set the real start time */
    jcr->jr.StartTime = jcr->start_time;
 
    if (jcr->job->MaxStartDelay != 0 && jcr->job->MaxStartDelay <
        (utime_t)(jcr->start_time - jcr->sched_time)) {
-      set_jcr_job_status(jcr, JS_Canceled);
+      jcr->setJobStatus(JS_Canceled);
       Jmsg(jcr, M_FATAL, 0, _("Job canceled because max start delay time exceeded.\n"));
    }
 
+   if (job_check_maxrunschedtime(jcr)) {
+      jcr->setJobStatus(JS_Canceled);
+      Jmsg(jcr, M_FATAL, 0, _("Job canceled because max run sched time exceeded.\n"));
+   }
+
    /* TODO : check if it is used somewhere */
    if (jcr->job->RunScripts == NULL) {
       Dmsg0(200, "Warning, job->RunScripts is empty\n");
@@ -265,69 +290,64 @@ static void *job_thread(void *arg)
    /* Run any script BeforeJob on dird */
    run_scripts(jcr, jcr->job->RunScripts, "BeforeJob");
 
-   if (job_canceled(jcr)) {
-      update_job_end(jcr, jcr->JobStatus);
+   /*
+    * We re-update the job start record so that the start
+    *  time is set after the run before job.  This avoids
+    *  that any files created by the run before job will
+    *  be saved twice.  They will be backed up in the current
+    *  job, but not in the next one unless they are changed.
+    *  Without this, they will be backed up in this job and
+    *  in the next job run because in that case, their date
+    *   is after the start of this run.
+    */
+   jcr->start_time = time(NULL);
+   jcr->jr.StartTime = jcr->start_time;
+   if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
+      Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
+   }
+   generate_job_event(jcr, "JobRun");
+   generate_plugin_event(jcr, bDirEventJobRun);
 
-   } else {
-      /*
-       * We re-update the job start record so that the start
-       *  time is set after the run before job.  This avoids
-       *  that any files created by the run before job will
-       *  be saved twice.  They will be backed up in the current
-       *  job, but not in the next one unless they are changed.
-       *  Without this, they will be backed up in this job and
-       *  in the next job run because in that case, their date
-       *   is after the start of this run.
-       */
-      jcr->start_time = time(NULL);
-      jcr->jr.StartTime = jcr->start_time;
-      if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
-         Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
+   switch (jcr->getJobType()) {
+   case JT_BACKUP:
+      if (!job_canceled(jcr) && do_backup(jcr)) {
+         do_autoprune(jcr);
+      } else {
+         backup_cleanup(jcr, JS_ErrorTerminated);
       }
-      generate_job_event(jcr, "JobRun");
-
-      switch (jcr->JobType) {
-      case JT_BACKUP:
-         if (do_backup(jcr)) {
-            do_autoprune(jcr);
-         } else {
-            backup_cleanup(jcr, JS_ErrorTerminated);
-         }
-         break;
-      case JT_VERIFY:
-         if (do_verify(jcr)) {
-            do_autoprune(jcr);
-         } else {
-            verify_cleanup(jcr, JS_ErrorTerminated);
-         }
-         break;
-      case JT_RESTORE:
-         if (do_restore(jcr)) {
-            do_autoprune(jcr);
-         } else {
-            restore_cleanup(jcr, JS_ErrorTerminated);
-         }
-         break;
-      case JT_ADMIN:
-         if (do_admin(jcr)) {
-            do_autoprune(jcr);
-         } else {
-            admin_cleanup(jcr, JS_ErrorTerminated);
-         }
-         break;
-      case JT_MIGRATE:
-      case JT_COPY:
-      case JT_ARCHIVE:
-         if (do_migration(jcr)) {
-            do_autoprune(jcr);
-         } else {
-            migration_cleanup(jcr, JS_ErrorTerminated);
-         }
-         break;
-      default:
-         Pmsg1(0, _("Unimplemented job type: %d\n"), jcr->JobType);
-         break;
+      break;
+   case JT_VERIFY:
+      if (!job_canceled(jcr) && do_verify(jcr)) {
+         do_autoprune(jcr);
+      } else {
+         verify_cleanup(jcr, JS_ErrorTerminated);
+      }
+      break;
+   case JT_RESTORE:
+      if (!job_canceled(jcr) && do_restore(jcr)) {
+         do_autoprune(jcr);
+      } else {
+         restore_cleanup(jcr, JS_ErrorTerminated);
+      }
+      break;
+   case JT_ADMIN:
+      if (!job_canceled(jcr) && do_admin(jcr)) {
+         do_autoprune(jcr);
+      } else {
+         admin_cleanup(jcr, JS_ErrorTerminated);
+      }
+      break;
+   case JT_COPY:
+   case JT_MIGRATE:
+      if (!job_canceled(jcr) && do_migration(jcr)) {
+         do_autoprune(jcr);
+      } else {
+         migration_cleanup(jcr, JS_ErrorTerminated);
       }
+      break;
+   default:
+      Pmsg1(0, _("Unimplemented job type: %d\n"), jcr->getJobType());
+      break;
    }
 
    run_scripts(jcr, jcr->job->RunScripts, "AfterJob");
@@ -338,11 +358,24 @@ static void *job_thread(void *arg)
    }
 
    generate_daemon_event(jcr, "JobEnd");
+   generate_plugin_event(jcr, bDirEventJobEnd);
    Dmsg1(50, "======== End Job stat=%c ==========\n", jcr->JobStatus);
-   sm_check(__FILE__, __LINE__, true);
+   Dsm_check(100);
    return NULL;
 }
 
+void sd_msg_thread_send_signal(JCR *jcr, int sig)
+{
+   jcr->lock();
+   if (  !jcr->sd_msg_thread_done
+       && jcr->SD_msg_chan 
+       && !pthread_equal(jcr->SD_msg_chan, pthread_self()))
+   {
+      Dmsg1(800, "Send kill to SD msg chan jid=%d\n", jcr->JobId);
+      pthread_kill(jcr->SD_msg_chan, sig);
+   }
+   jcr->unlock();
+}
 
 /*
  * Cancel a job -- typically called by the UA (Console program), but may also
@@ -355,10 +388,11 @@ bool cancel_job(UAContext *ua, JCR *jcr)
 {
    BSOCK *sd, *fd;
    char ed1[50];
+   int32_t old_status = jcr->JobStatus;
 
-   set_jcr_job_status(jcr, JS_Canceled);
+   jcr->setJobStatus(JS_Canceled);
 
-   switch (jcr->JobStatus) {
+   switch (old_status) {
    case JS_Created:
    case JS_WaitJobRes:
    case JS_WaitClientRes:
@@ -369,7 +403,7 @@ bool cancel_job(UAContext *ua, JCR *jcr)
       ua->info_msg(_("JobId %s, Job %s marked to be canceled.\n"),
               edit_uint64(jcr->JobId, ed1), jcr->Job);
       jobq_remove(&job_queue, jcr); /* attempt to remove it from queue */
-      return true;
+      break;
 
    default:
       /* Cancel File daemon */
@@ -381,13 +415,15 @@ bool cancel_job(UAContext *ua, JCR *jcr)
          }
          Dmsg0(200, "Connected to file daemon\n");
          fd = ua->jcr->file_bsock;
-         bnet_fsend(fd, "cancel Job=%s\n", jcr->Job);
-         while (bnet_recv(fd) >= 0) {
+         fd->fsend("cancel Job=%s\n", jcr->Job);
+         while (fd->recv() >= 0) {
             ua->send_msg("%s", fd->msg);
          }
-         bnet_sig(fd, BNET_TERMINATE);
-         bnet_close(fd);
+         fd->signal(BNET_TERMINATE);
+         fd->close();
          ua->jcr->file_bsock = NULL;
+         jcr->file_bsock->set_terminated();
+         jcr->my_thread_send_signal(TIMEOUT_SIGNAL);
       }
 
       /* Cancel Storage daemon */
@@ -421,7 +457,12 @@ bool cancel_job(UAContext *ua, JCR *jcr)
          sd->signal(BNET_TERMINATE);
          sd->close();
          ua->jcr->store_bsock = NULL;
+         jcr->store_bsock->set_timed_out();
+         jcr->store_bsock->set_terminated();
+         sd_msg_thread_send_signal(jcr, TIMEOUT_SIGNAL);
+         jcr->my_thread_send_signal(TIMEOUT_SIGNAL);
       }
+      break;
    }
 
    return true;
@@ -429,6 +470,10 @@ bool cancel_job(UAContext *ua, JCR *jcr)
 
 void cancel_storage_daemon_job(JCR *jcr)
 {
+   if (jcr->sd_canceled) { 
+      return;                   /* cancel only once */
+   }
+
    UAContext *ua = new_ua_context(jcr);
    JCR *control_jcr = new_control_jcr("*JobCancel*", JT_SYSTEM);
    BSOCK *sd;
@@ -462,6 +507,11 @@ void cancel_storage_daemon_job(JCR *jcr)
       sd->signal(BNET_TERMINATE);
       sd->close();
       ua->jcr->store_bsock = NULL;
+      jcr->sd_canceled = true;
+      jcr->store_bsock->set_timed_out();
+      jcr->store_bsock->set_terminated();
+      sd_msg_thread_send_signal(jcr, TIMEOUT_SIGNAL);
+      jcr->my_thread_send_signal(TIMEOUT_SIGNAL);
    }
 bail_out:
    free_jcr(control_jcr);
@@ -481,26 +531,31 @@ static void job_monitor_watchdog(watchdog_t *self)
 
    control_jcr = (JCR *)self->data;
 
-   Dsm_check(1);
+   Dsm_check(100);
    Dmsg1(800, "job_monitor_watchdog %p called\n", self);
 
    foreach_jcr(jcr) {
       bool cancel = false;
 
-      if (jcr->JobId == 0 || job_canceled(jcr)) {
+      if (jcr->JobId == 0 || job_canceled(jcr) || jcr->no_maxtime) {
          Dmsg2(800, "Skipping JCR=%p Job=%s\n", jcr, jcr->Job);
          continue;
       }
 
       /* check MaxWaitTime */
-      if (job_check_maxwaittime(control_jcr, jcr)) {
-         set_jcr_job_status(jcr, JS_Canceled);
-         Jmsg(jcr, M_FATAL, 0, _("Max wait time exceeded. Job canceled.\n"));
+      if (job_check_maxwaittime(jcr)) {
+         jcr->setJobStatus(JS_Canceled);
+         Qmsg(jcr, M_FATAL, 0, _("Max wait time exceeded. Job canceled.\n"));
          cancel = true;
       /* check MaxRunTime */
-      } else if (job_check_maxruntime(control_jcr, jcr)) {
-         set_jcr_job_status(jcr, JS_Canceled);
-         Jmsg(jcr, M_FATAL, 0, _("Max run time exceeded. Job canceled.\n"));
+      } else if (job_check_maxruntime(jcr)) {
+         jcr->setJobStatus(JS_Canceled);
+         Qmsg(jcr, M_FATAL, 0, _("Max run time exceeded. Job canceled.\n"));
+         cancel = true;
+      /* check MaxRunSchedTime */ 
+      } else if (job_check_maxrunschedtime(jcr)) {
+         jcr->setJobStatus(JS_Canceled);
+         Qmsg(jcr, M_FATAL, 0, _("Max run sched time exceeded. Job canceled.\n"));
          cancel = true;
       }
 
@@ -522,47 +577,84 @@ static void job_monitor_watchdog(watchdog_t *self)
  * Check if the maxwaittime has expired and it is possible
  *  to cancel the job.
  */
-static bool job_check_maxwaittime(JCR *control_jcr, JCR *jcr)
+static bool job_check_maxwaittime(JCR *jcr)
 {
    bool cancel = false;
    JOB *job = jcr->job;
+   utime_t current=0;
 
-   if (job_canceled(jcr)) {
-      return false;                /* already canceled */
+   if (!job_waiting(jcr)) {
+      return false;
+   }
+
+   if (jcr->wait_time) {
+      current = watchdog_time - jcr->wait_time;
+   }
+
+   Dmsg2(200, "check maxwaittime %u >= %u\n", 
+         current + jcr->wait_time_sum, job->MaxWaitTime);
+   if (job->MaxWaitTime != 0 &&
+       (current + jcr->wait_time_sum) >= job->MaxWaitTime) {
+      cancel = true;
+   }
+
+   return cancel;
+}
+
+/*
+ * Check if maxruntime has expired and if the job can be
+ *   canceled.
+ */
+static bool job_check_maxruntime(JCR *jcr)
+{
+   bool cancel = false;
+   JOB *job = jcr->job;
+   utime_t run_time;
+
+   if (job_canceled(jcr) || jcr->JobStatus == JS_Created) {
+      return false;
    }
-   if (job->MaxWaitTime == 0 && job->FullMaxWaitTime == 0 &&
-       job->IncMaxWaitTime == 0 && job->DiffMaxWaitTime == 0) {
+   if (jcr->job->MaxRunTime == 0 && job->FullMaxRunTime == 0 &&
+       job->IncMaxRunTime == 0 && job->DiffMaxRunTime == 0) {
       return false;
-   } 
-   if (jcr->JobLevel == L_FULL && job->FullMaxWaitTime != 0 &&
-         (watchdog_time - jcr->start_time) >= job->FullMaxWaitTime) {
+   }
+   run_time = watchdog_time - jcr->start_time;
+   Dmsg7(200, "check_maxruntime %llu-%u=%llu >= %llu|%llu|%llu|%llu\n",
+         watchdog_time, jcr->start_time, run_time, job->MaxRunTime, job->FullMaxRunTime, 
+         job->IncMaxRunTime, job->DiffMaxRunTime);
+
+   if (jcr->getJobLevel() == L_FULL && job->FullMaxRunTime != 0 &&
+         run_time >= job->FullMaxRunTime) {
+      Dmsg0(200, "check_maxwaittime: FullMaxcancel\n");
       cancel = true;
-   } else if (jcr->JobLevel == L_DIFFERENTIAL && job->DiffMaxWaitTime != 0 &&
-         (watchdog_time - jcr->start_time) >= job->DiffMaxWaitTime) {
+   } else if (jcr->getJobLevel() == L_DIFFERENTIAL && job->DiffMaxRunTime != 0 &&
+         run_time >= job->DiffMaxRunTime) {
+      Dmsg0(200, "check_maxwaittime: DiffMaxcancel\n");
       cancel = true;
-   } else if (jcr->JobLevel == L_INCREMENTAL && job->IncMaxWaitTime != 0 &&
-         (watchdog_time - jcr->start_time) >= job->IncMaxWaitTime) {
+   } else if (jcr->getJobLevel() == L_INCREMENTAL && job->IncMaxRunTime != 0 &&
+         run_time >= job->IncMaxRunTime) {
+      Dmsg0(200, "check_maxwaittime: IncMaxcancel\n");
       cancel = true;
-   } else if (job->MaxWaitTime != 0 &&
-         (watchdog_time - jcr->start_time) >= job->MaxWaitTime) {
+   } else if (job->MaxRunTime > 0 && run_time >= job->MaxRunTime) {
+      Dmsg0(200, "check_maxwaittime: Maxcancel\n");
       cancel = true;
    }
-
    return cancel;
 }
 
 /*
- * Check if maxruntime has expired and if the job can be
+ * Check if MaxRunSchedTime has expired and if the job can be
  *   canceled.
  */
-static bool job_check_maxruntime(JCR *control_jcr, JCR *jcr)
+static bool job_check_maxrunschedtime(JCR *jcr)
 {
-   if (jcr->job->MaxRunTime == 0 || job_canceled(jcr) || jcr->JobStatus == JS_Created) {
+   if (jcr->MaxRunSchedTime == 0 || job_canceled(jcr)) {
       return false;
    }
-   if ((watchdog_time - jcr->start_time) < jcr->job->MaxRunTime) {
-      Dmsg3(200, "Job %p (%s) with MaxRunTime %d not expired\n",
-            jcr, jcr->Job, jcr->job->MaxRunTime);
+   if ((watchdog_time - jcr->sched_time) < jcr->MaxRunSchedTime) {
+      Dmsg3(200, "Job %p (%s) with MaxRunSchedTime %d not expired\n",
+            jcr, jcr->Job, jcr->MaxRunSchedTime);
       return false;
    }
 
@@ -585,28 +677,161 @@ 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. %s"), pr.Name,
+         Jmsg(jcr, M_FATAL, 0, _("Pool \"%s\" not in database. ERR=%s"), pr.Name,
             db_strerror(jcr->db));
          return 0;
       } else {
-         Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
+         Jmsg(jcr, M_INFO, 0, _("Created database record for Pool \"%s\".\n"), pr.Name);
       }
    }
    return pr.PoolId;
 }
 
+/*
+ * Check for duplicate jobs.
+ *  Returns: true  if current job should continue
+ *           false if current job should terminate
+ */
+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.
+    */
+   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) {
+         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.
+       */
+      if (djcr->IgnoreDuplicateJobChecking) {
+         continue;
+      }
+
+      if (strcmp(job->name(), djcr->job->name()) == 0) {
+         if (job->DuplicateJobProximity > 0) {
+            utime_t now = (utime_t)time(NULL);
+            if ((now - djcr->start_time) > job->DuplicateJobProximity) {
+               continue;               /* not really a duplicate */
+            }
+         }
+         if (job->CancelLowerLevelDuplicates &&                         
+             djcr->getJobType() == 'B' && jcr->getJobType() == 'B') {
+            switch (jcr->getJobLevel()) {
+            case L_FULL:
+               if (djcr->getJobLevel() == L_DIFFERENTIAL ||
+                   djcr->getJobLevel() == L_INCREMENTAL) {
+                  cancel_dup = true;
+               }
+               break;
+            case L_DIFFERENTIAL:
+               if (djcr->getJobLevel() == L_INCREMENTAL) {
+                  cancel_dup = true;
+               }
+               if (djcr->getJobLevel() == L_FULL) {
+                  cancel_me = true;
+               }
+               break;
+            case L_INCREMENTAL:
+               if (djcr->getJobLevel() == L_FULL ||
+                   djcr->getJobLevel() == L_DIFFERENTIAL) {
+                  cancel_me = true;
+               }
+            }
+            /*
+             * cancel_dup will be done below   
+             */
+            if (cancel_me) {
+              /* Zap current job */
+              Jmsg(jcr, M_FATAL, 0, _("JobId %d already running. Duplicate job not allowed.\n"),
+                 djcr->JobId);
+              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.
+          */
+         if (job->CancelQueuedDuplicates) {
+             switch (djcr->JobStatus) {
+             case JS_Created:
+             case JS_WaitJobRes:
+             case JS_WaitClientRes:
+             case JS_WaitStoreRes:
+             case JS_WaitPriority:
+             case JS_WaitMaxJobs:
+             case JS_WaitStartTime:
+                cancel_dup = true;  /* cancel queued duplicate */
+                break;
+             default:
+                break;
+             }
+         }
+
+         if (cancel_dup || job->CancelRunningDuplicates) {
+            /*
+             * 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);
+            bmicrosleep(0, 500000);
+            cancel_job(ua, djcr);
+            free_ua_context(ua);
+            Dmsg2(800, "Cancel dup %p JobId=%d\n", djcr, djcr->JobId);
+         } else {
+            /*
+             * Zap current job
+             */
+            Jmsg(jcr, M_FATAL, 0, _("JobId %d already running. Duplicate job not allowed.\n"),
+               djcr->JobId);
+            Dmsg2(800, "Cancel me %p JobId=%d\n", jcr, jcr->JobId);
+         }
+         Dmsg4(800, "curJobId=%d use_cnt=%d dupJobId=%d use_cnt=%d\n",
+               jcr->JobId, jcr->use_count(), djcr->JobId, djcr->use_count());
+         break;                 /* did our work, get out of foreach loop */
+      }
+   }
+   endeach_jcr(djcr);
+
+   return true;   
+}
+
 void apply_pool_overrides(JCR *jcr)
 {
+   bool pool_override = false;
+
    if (jcr->run_pool_override) {
       pm_strcpy(jcr->pool_source, _("Run pool override"));
    }
    /*
     * Apply any level related Pool selections
     */
-   switch (jcr->JobLevel) {
+   switch (jcr->getJobLevel()) {
    case L_FULL:
       if (jcr->full_pool) {
          jcr->pool = jcr->full_pool;
+         pool_override = true;
          if (jcr->run_full_pool_override) {
             pm_strcpy(jcr->pool_source, _("Run FullPool override"));
          } else {
@@ -617,6 +842,7 @@ void apply_pool_overrides(JCR *jcr)
    case L_INCREMENTAL:
       if (jcr->inc_pool) {
          jcr->pool = jcr->inc_pool;
+         pool_override = true;
          if (jcr->run_inc_pool_override) {
             pm_strcpy(jcr->pool_source, _("Run IncPool override"));
          } else {
@@ -627,6 +853,7 @@ void apply_pool_overrides(JCR *jcr)
    case L_DIFFERENTIAL:
       if (jcr->diff_pool) {
          jcr->pool = jcr->diff_pool;
+         pool_override = true;
          if (jcr->run_diff_pool_override) {
             pm_strcpy(jcr->pool_source, _("Run DiffPool override"));
          } else {
@@ -635,6 +862,11 @@ void apply_pool_overrides(JCR *jcr)
       }
       break;
    }
+   /* Update catalog if pool overridden */
+   if (pool_override && jcr->pool->catalog) {
+      jcr->catalog = jcr->pool->catalog;
+      pm_strcpy(jcr->catalog_source, _("Pool resource"));
+   }
 }
 
 
@@ -713,8 +945,8 @@ void init_jcr_job_record(JCR *jcr)
    jcr->jr.SchedTime = jcr->sched_time;
    jcr->jr.StartTime = jcr->start_time;
    jcr->jr.EndTime = 0;               /* perhaps rescheduled, clear it */
-   jcr->jr.JobType = jcr->JobType;
-   jcr->jr.JobLevel = jcr->JobLevel;
+   jcr->jr.JobType = jcr->getJobType();
+   jcr->jr.JobLevel = jcr->getJobLevel();
    jcr->jr.JobStatus = jcr->JobStatus;
    jcr->jr.JobId = jcr->JobId;
    bstrncpy(jcr->jr.Name, jcr->job->name(), sizeof(jcr->jr.Name));
@@ -732,9 +964,11 @@ void update_job_end_record(JCR *jcr)
    jcr->jr.JobStatus = jcr->JobStatus;
    jcr->jr.JobFiles = jcr->JobFiles;
    jcr->jr.JobBytes = jcr->JobBytes;
+   jcr->jr.ReadBytes = jcr->ReadBytes;
    jcr->jr.VolSessionId = jcr->VolSessionId;
    jcr->jr.VolSessionTime = jcr->VolSessionTime;
-   jcr->jr.JobErrors = jcr->Errors;
+   jcr->jr.JobErrors = jcr->JobErrors;
+   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"),
          db_strerror(jcr->db));
@@ -758,17 +992,17 @@ void create_unique_job_name(JCR *jcr, const char *base_name)
    static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    static time_t last_start_time = 0;
    static int seq = 0;
-   time_t now;
+   time_t now = time(NULL);
    struct tm tm;
    char dt[MAX_TIME_LENGTH];
    char name[MAX_NAME_LENGTH];
    char *p;
+   int len;
 
    /* Guarantee unique start time -- maximum one per second, and
     * thus unique Job Name
     */
    P(mutex);                          /* lock creation of jobs */
-   now = time(NULL);
    seq++;
    if (seq > 59) {                    /* wrap as if it is seconds */
       seq = 0;
@@ -783,29 +1017,23 @@ void create_unique_job_name(JCR *jcr, const char *base_name)
    /* Form Unique JobName */
    (void)localtime_r(&now, &tm);
    /* Use only characters that are permitted in Windows filenames */
-   strftime(dt, sizeof(dt), "%Y-%m-%d_%H.%M", &tm);
+   strftime(dt, sizeof(dt), "%Y-%m-%d_%H.%M.%S", &tm);
+   len = strlen(dt) + 5;   /* dt + .%02d EOS */
    bstrncpy(name, base_name, sizeof(name));
-   name[sizeof(name)-22] = 0;          /* truncate if too long */
-   bsnprintf(jcr->Job, sizeof(jcr->Job), "%s.%s.%02d", name, dt, seq); /* add date & time */
+   name[sizeof(name)-len] = 0;          /* truncate if too long */
+   bsnprintf(jcr->Job, sizeof(jcr->Job), "%s.%s_%02d", name, dt, seq); /* add date & time */
    /* Convert spaces into underscores */
    for (p=jcr->Job; *p; p++) {
       if (*p == ' ') {
          *p = '_';
       }
    }
+   Dmsg2(100, "JobId=%u created Job=%s\n", jcr->JobId, jcr->Job);
 }
 
 /* Called directly from job rescheduling */
 void dird_free_jcr_pointers(JCR *jcr)
 {
-   if (jcr->sd_auth_key) {
-      free(jcr->sd_auth_key);
-      jcr->sd_auth_key = NULL;
-   }
-   if (jcr->where) {
-      free(jcr->where);
-      jcr->where = NULL;
-   }
    if (jcr->file_bsock) {
       Dmsg0(200, "Close File bsock\n");
       bnet_close(jcr->file_bsock);
@@ -816,27 +1044,16 @@ void dird_free_jcr_pointers(JCR *jcr)
       bnet_close(jcr->store_bsock);
       jcr->store_bsock = NULL;
    }
-   if (jcr->fname) {
-      Dmsg0(200, "Free JCR fname\n");
-      free_pool_memory(jcr->fname);
-      jcr->fname = NULL;
-   }
-   if (jcr->RestoreBootstrap) {
-      free(jcr->RestoreBootstrap);
-      jcr->RestoreBootstrap = NULL;
-   }
-   if (jcr->client_uname) {
-      free_pool_memory(jcr->client_uname);
-      jcr->client_uname = NULL;
-   }
-   if (jcr->attr) {
-      free_pool_memory(jcr->attr);
-      jcr->attr = NULL;
-   }
-   if (jcr->ar) {
-      free(jcr->ar);
-      jcr->ar = NULL;
-   }
+
+   bfree_and_null(jcr->sd_auth_key);
+   bfree_and_null(jcr->where);
+   bfree_and_null(jcr->RestoreBootstrap);
+   bfree_and_null(jcr->ar);
+
+   free_and_null_pool_memory(jcr->JobIds);
+   free_and_null_pool_memory(jcr->client_uname);
+   free_and_null_pool_memory(jcr->attr);
+   free_and_null_pool_memory(jcr->fname);
 }
 
 /*
@@ -853,40 +1070,23 @@ void dird_free_jcr(JCR *jcr)
       pthread_cond_destroy(&jcr->term_wait);
       jcr->term_wait_inited = false;
    }
-   if (jcr->db_batch && jcr->db_batch != jcr->db) {
+   if (jcr->db_batch) {
       db_close_database(jcr, jcr->db_batch);
+      jcr->db_batch = NULL;
+      jcr->batch_started = false;
    }
-   jcr->db_batch = NULL;
    if (jcr->db) {
       db_close_database(jcr, jcr->db);
       jcr->db = NULL;
    }
-   if (jcr->stime) {
-      Dmsg0(200, "Free JCR stime\n");
-      free_pool_memory(jcr->stime);
-      jcr->stime = NULL;
-   }
-   if (jcr->fname) {
-      Dmsg0(200, "Free JCR fname\n");
-      free_pool_memory(jcr->fname);
-      jcr->fname = NULL;
-   }
-   if (jcr->pool_source) {
-      free_pool_memory(jcr->pool_source);
-      jcr->pool_source = NULL;
-   }
-   if (jcr->rpool_source) {
-      free_pool_memory(jcr->rpool_source);
-      jcr->rpool_source = NULL;
-   }
-   if (jcr->wstore_source) {
-      free_pool_memory(jcr->wstore_source);
-      jcr->wstore_source = NULL;
-   }
-   if (jcr->rstore_source) {
-      free_pool_memory(jcr->rstore_source);
-      jcr->rstore_source = NULL;
-   }
+
+   free_and_null_pool_memory(jcr->stime);
+   free_and_null_pool_memory(jcr->fname);
+   free_and_null_pool_memory(jcr->pool_source);
+   free_and_null_pool_memory(jcr->catalog_source);
+   free_and_null_pool_memory(jcr->rpool_source);
+   free_and_null_pool_memory(jcr->wstore_source);
+   free_and_null_pool_memory(jcr->rstore_source);
 
    /* Delete lists setup to hold storage pointers */
    free_rwstorage(jcr);
@@ -896,6 +1096,8 @@ void dird_free_jcr(JCR *jcr)
    if (jcr->JobId != 0)
       write_state_file(director->working_directory, "bacula-dir", get_first_port_host_order(director->DIRaddrs));
 
+   free_plugins(jcr);                 /* release instantiated plugins */
+
    Dmsg0(200, "End dird free_jcr\n");
 }
 
@@ -935,23 +1137,18 @@ void get_job_storage(USTORE *store, JOB *job, RUN *run)
 void set_jcr_defaults(JCR *jcr, JOB *job)
 {
    jcr->job = job;
-   jcr->JobType = job->JobType;
+   jcr->setJobType(job->JobType);
    jcr->JobStatus = JS_Created;
-   switch (jcr->JobType) {
+
+   switch (jcr->getJobType()) {
    case JT_ADMIN:
-   case JT_RESTORE:
-      jcr->JobLevel = L_NONE;
+      jcr->setJobLevel(L_NONE);
       break;
-   case JT_MIGRATE:
-      if (!jcr->rpool_source) {
-         jcr->rpool_source = get_pool_memory(PM_MESSAGE);
-         pm_strcpy(jcr->rpool_source, _("unknown source"));
-      }
-      /* Fall-through wanted */
    default:
-      jcr->JobLevel = job->JobLevel;
+      jcr->setJobLevel(job->JobLevel);
       break;
    }
+
    if (!jcr->fname) {
       jcr->fname = get_pool_memory(PM_FNAME);
    }
@@ -959,6 +1156,10 @@ void set_jcr_defaults(JCR *jcr, JOB *job)
       jcr->pool_source = get_pool_memory(PM_MESSAGE);
       pm_strcpy(jcr->pool_source, _("unknown source"));
    }
+   if (!jcr->catalog_source) {
+      jcr->catalog_source = get_pool_memory(PM_MESSAGE);
+      pm_strcpy(jcr->catalog_source, _("unknown source"));
+   }
 
    jcr->JobPriority = job->Priority;
    /* Copy storage definitions -- deleted in dir_free_jcr above */
@@ -977,12 +1178,21 @@ void set_jcr_defaults(JCR *jcr, JOB *job)
    jcr->full_pool = job->full_pool;
    jcr->inc_pool = job->inc_pool;
    jcr->diff_pool = job->diff_pool;
-   jcr->catalog = job->client->catalog;
+   if (job->pool->catalog) {
+      jcr->catalog = job->pool->catalog;
+      pm_strcpy(jcr->catalog_source, _("Pool resource"));
+   } else {
+      jcr->catalog = job->client->catalog;
+      pm_strcpy(jcr->catalog_source, _("Client resource"));
+   }
    jcr->fileset = job->fileset;
+   jcr->accurate = job->accurate;
    jcr->messages = job->messages;
    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;
@@ -994,20 +1204,20 @@ void set_jcr_defaults(JCR *jcr, JOB *job)
    /* This can be overridden by Console program */
    jcr->verify_job = job->verify_job;
    /* If no default level given, set one */
-   if (jcr->JobLevel == 0) {
-      switch (jcr->JobType) {
+   if (jcr->getJobLevel() == 0) {
+      switch (jcr->getJobType()) {
       case JT_VERIFY:
-         jcr->JobLevel = L_VERIFY_CATALOG;
+         jcr->setJobLevel(L_VERIFY_CATALOG);
          break;
       case JT_BACKUP:
-         jcr->JobLevel = L_INCREMENTAL;
+         jcr->setJobLevel(L_INCREMENTAL);
          break;
       case JT_RESTORE:
       case JT_ADMIN:
-         jcr->JobLevel = L_NONE;
+         jcr->setJobLevel(L_NONE);
          break;
       default:
-         jcr->JobLevel = L_FULL;
+         jcr->setJobLevel(L_FULL);
          break;
       }
    }
@@ -1018,16 +1228,10 @@ void set_jcr_defaults(JCR *jcr, JOB *job)
  */
 void copy_rwstorage(JCR *jcr, alist *storage, const char *where)
 {
-   switch(jcr->JobType) {
-   case JT_RESTORE:
-   case JT_VERIFY:
-   case JT_MIGRATE:
+   if (jcr->JobReads()) {
       copy_rstorage(jcr, storage, where);
-      break;
-   default:
-      copy_wstorage(jcr, storage, where);
-      break;
    }
+   copy_wstorage(jcr, storage, where);
 }
 
 
@@ -1038,16 +1242,10 @@ void set_rwstorage(JCR *jcr, USTORE *store)
       Jmsg(jcr, M_FATAL, 0, _("No storage specified.\n"));
       return;
    }
-   switch(jcr->JobType) {
-   case JT_RESTORE:
-   case JT_VERIFY:
-   case JT_MIGRATE:
+   if (jcr->JobReads()) {
       set_rstorage(jcr, store);
-      break;
-   default:
-      set_wstorage(jcr, store);
-      break;
    }
+   set_wstorage(jcr, store);
 }
 
 void free_rwstorage(JCR *jcr)
@@ -1196,13 +1394,14 @@ void create_clones(JCR *jcr)
       UAContext *ua = new_ua_context(jcr);
       ua->batch = true;
       foreach_alist(runcmd, job->run_cmds) {
-         cmd = edit_job_codes(jcr, cmd, runcmd, "");              
+         cmd = edit_job_codes(jcr, cmd, runcmd, "", job_code_callback_director);
          Mmsg(ua->cmd, "run %s cloned=yes", cmd);
          Dmsg1(900, "=============== Clone cmd=%s\n", ua->cmd);
          parse_ua_args(ua);                 /* parse command */
          int stat = run_cmd(ua, ua->cmd);
          if (stat == 0) {
-            Jmsg(jcr, M_ERROR, 0, _("Could not start clone job.\n"));
+            Jmsg(jcr, M_ERROR, 0, _("Could not start clone job: \"%s\".\n"),
+                 ua->cmd);
          } else {
             Jmsg(jcr, M_INFO, 0, _("Clone JobId %d started.\n"), stat);
          }
@@ -1212,40 +1411,60 @@ void create_clones(JCR *jcr)
    }
 }
 
-bool create_restore_bootstrap_file(JCR *jcr)
+/*
+ * 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)
 {
    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;
    ua = new_ua_context(jcr);
-   complete_bsr(ua, rx.bsr);
+   if (!complete_bsr(ua, rx.bsr)) {
+      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) {
-      free_ua_context(ua);
-      free_bsr(rx.bsr);
-      return false;
+      files = 0;
+      goto bail_out;
    }
    free_ua_context(ua);
    free_bsr(rx.bsr);
    jcr->needs_sd = true;
-   return true;
+   return jcr->ExpectedFiles;
+
+bail_out:
+   free_ua_context(ua);
+   free_bsr(rx.bsr);
+   return files;
 }
 
-bool run_console_command(JCR *jcr, const char *cmd){
+/* TODO: redirect command ouput to job log */
+bool run_console_command(JCR *jcr, const char *cmd)
+{
    UAContext *ua;
    bool ok;
-
-   ua = new_ua_context(jcr);
+   JCR *ljcr = new_control_jcr("-RunScript-", JT_CONSOLE);
+   ua = new_ua_context(ljcr);
+   /* run from runscript and check if commands are autorized */
+   ua->runscript = true;
    Mmsg(ua->cmd, "%s", cmd);
    Dmsg1(100, "Console command: %s\n", ua->cmd);
    parse_ua_args(ua);
    ok= do_a_command(ua);
    free_ua_context(ua);
+   free_jcr(ljcr);
    return ok;
 }