]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/dird/migrate.c
kes Fix memory leak with storage ids in cats/sql_get.c
[bacula/bacula] / bacula / src / dird / migrate.c
index 151a4e62606e1899c9dc861fde3799c76ccdd626..6d21bd8a4af09ee4ffb40366ca3f91ab4940cdb0 100644 (file)
@@ -54,7 +54,7 @@
 static const int dbglevel = 10;
 
 static char OKbootstrap[] = "3000 OK bootstrap\n";
-static bool get_job_to_migrate(JCR *jcr);
+static int get_job_to_migrate(JCR *jcr);
 struct idpkt;
 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
                  const char *query2, const char *type);
@@ -87,7 +87,10 @@ static int get_next_dbid_from_list(char **p, DBId_t *DBId);
  *    the current jcr.  It is a backup job that moves (migrates) the
  *    data written for the previous_jr into the new pool.  This
  *    job (mig_jcr) becomes the new backup job that replaces
- *    the original backup job.
+ *    the original backup job. Note, this jcr is not really run. It
+ *    is simply attached to the current jcr.  It will show up in
+ *    the Director's status output, but not in the SD or FD, both of
+ *    which deal only with the current migration job (i.e. jcr).
  */
 bool do_migration_init(JCR *jcr)
 {
@@ -96,11 +99,38 @@ bool do_migration_init(JCR *jcr)
    char ed1[100];
    JOB *job, *prev_job;
    JCR *mig_jcr;                   /* newly migrated job */
+   int count;
+
+
+   apply_pool_overrides(jcr);
+
+   jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->name());
+   if (jcr->jr.PoolId == 0) {
+      Dmsg1(dbglevel, "JobId=%d no PoolId\n", (int)jcr->JobId);
+      Jmsg(jcr, M_FATAL, 0, _("Could not get or create a Pool record.\n"));
+      return false;
+   }
+   /*
+    * Note, at this point, pool is the pool for this job.  We
+    *  transfer it to rpool (read pool), and a bit later,
+    *  pool will be changed to point to the write pool, 
+    *  which comes from pool->NextPool.
+    */
+   jcr->rpool = jcr->pool;            /* save read pool */
+   pm_strcpy(jcr->rpool_source, jcr->pool_source);
+
+
+   Dmsg2(dbglevel, "Read pool=%s (From %s)\n", jcr->rpool->name(), jcr->rpool_source);
 
    /* If we find a job or jobs to migrate it is previous_jr.JobId */
-   if (!get_job_to_migrate(jcr)) {
+   count = get_job_to_migrate(jcr);
+   if (count < 0) {
       return false;
    }
+   if (count == 0) {
+      return true;
+   }
+
    Dmsg1(dbglevel, "Back from get_job_to_migrate JobId=%d\n", (int)jcr->JobId);
 
    if (jcr->previous_jr.JobId == 0) {
@@ -115,15 +145,6 @@ bool do_migration_init(JCR *jcr)
       return false;
    }
 
-   apply_pool_overrides(jcr);
-
-   jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->hdr.name);
-   if (jcr->jr.PoolId == 0) {
-      Dmsg1(dbglevel, "JobId=%d no PoolId\n", (int)jcr->JobId);
-      Jmsg(jcr, M_FATAL, 0, _("Could not get or create a Pool record.\n"));
-      return false;
-   }
-
    create_restore_bootstrap_file(jcr);
 
    if (jcr->previous_jr.JobId == 0 || jcr->ExpectedFiles == 0) {
@@ -214,32 +235,19 @@ bool do_migration_init(JCR *jcr)
     *  will be migrating from pool to pool->NextPool.
     */
    if (pool->NextPool) {
-      jcr->jr.PoolId = get_or_create_pool_record(jcr, pool->NextPool->hdr.name);
+      jcr->jr.PoolId = get_or_create_pool_record(jcr, pool->NextPool->name());
       if (jcr->jr.PoolId == 0) {
          return false;
       }
-      /*
-       * put the "NextPool" resource pointer in our jcr so that we
-       * can pull the Storage reference from it.
-       */
-      mig_jcr->pool = jcr->pool = pool->NextPool;
-      mig_jcr->jr.PoolId = jcr->jr.PoolId;
-      pm_strcpy(jcr->pool_source, _("NextPool in Pool resource"));
-   } else {
-      Jmsg(jcr, M_FATAL, 0, _("No Next Pool specification found in Pool \"%s\".\n"),
-         pool->hdr.name);
-      return false;
    }
-
-   if (!jcr->pool->storage || jcr->pool->storage->size() == 0) {
-      Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Next Pool \"%s\".\n"),
-         jcr->pool->hdr.name);
+   if (!set_migration_wstorage(jcr, pool)) {
       return false;
    }
+   mig_jcr->pool = jcr->pool = pool->NextPool;
+   pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
+   mig_jcr->jr.PoolId = jcr->jr.PoolId;
 
-   /* If pool storage specified, use it instead of job storage for backup */
-   copy_wstorage(jcr, jcr->pool->storage, _("NextPool in Pool resource"));
-
+   Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
    return true;
 }
 
@@ -269,25 +277,6 @@ bool do_migration(JCR *jcr)
    Jmsg(jcr, M_INFO, 0, _("Start Migration JobId %s, Job=%s\n"),
         edit_uint64(jcr->JobId, ed1), jcr->Job);
 
-   set_jcr_job_status(jcr, JS_Running);
-   set_jcr_job_status(mig_jcr, JS_Running);
-   Dmsg2(dbglevel, "JobId=%d JobLevel=%c\n", (int)jcr->jr.JobId, jcr->jr.JobLevel);
-
-   /* Update job start record for this migration control job */
-   if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
-      Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
-      return false;
-   }
-
-   Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
-      mig_jcr->jr.Name, (int)mig_jcr->jr.JobId, 
-      mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
-
-   /* Update job start record for the real migration backup job */
-   if (!db_update_job_start_record(mig_jcr, mig_jcr->db, &mig_jcr->jr)) {
-      Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(mig_jcr->db));
-      return false;
-   }
 
 
    /*
@@ -327,6 +316,49 @@ bool do_migration(JCR *jcr)
       return false;
    }
 
+   /*    
+    * 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;
+   jcr->jr.JobTDate = jcr->start_time;
+   set_jcr_job_status(jcr, JS_Running);
+
+   /* Update job start record for this migration control job */
+   if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
+      Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
+      return false;
+   }
+
+
+   mig_jcr->start_time = time(NULL);
+   mig_jcr->jr.StartTime = mig_jcr->start_time;
+   mig_jcr->jr.JobTDate = mig_jcr->start_time;
+   set_jcr_job_status(mig_jcr, JS_Running);
+
+   /* Update job start record for the real migration backup job */
+   if (!db_update_job_start_record(mig_jcr, mig_jcr->db, &mig_jcr->jr)) {
+      Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(mig_jcr->db));
+      return false;
+   }
+
+   Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
+      mig_jcr->jr.Name, (int)mig_jcr->jr.JobId, 
+      mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
+
+
+   /*
+    * Start the job prior to starting the message thread below
+    * to avoid two threads from using the BSOCK structure at
+    * the same time.
+    */
    if (!bnet_fsend(sd, "run")) {
       return false;
    }
@@ -465,7 +497,7 @@ const char *sql_client =
 const char *sql_jobids_from_client =
    "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
    " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
-   " AND Job.ClientId=Client.ClientId "
+   " AND Job.ClientId=Client.ClientId AND Job.Type='B'"
    " ORDER by Job.StartTime";
 
 /* Get Volume names in Pool */
@@ -478,7 +510,7 @@ const char *sql_vol =
 const char *sql_jobids_from_vol =
    "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
    " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
-   " AND JobMedia.JobId=Job.JobId
+   " AND JobMedia.JobId=Job.JobId AND Job.Type='B'"
    " ORDER by Job.StartTime";
 
 
@@ -498,6 +530,7 @@ const char *sql_oldest_vol =
 const char *sql_jobids_from_mediaid =
    "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
    " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId=%s"
+   " AND Job.Type='B'"
    " ORDER by Job.StartTime";
 
 /* Get tne number of bytes in the pool */
@@ -522,6 +555,7 @@ const char *sql_pool_time =
    "SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
    " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
    " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
+   " Job.Type='B' AND"
    " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
    " AND Job.RealEndTime<='%s'";
 
@@ -547,10 +581,11 @@ const char *sql_pool_time =
  *   one starting a new job with MigrationJobId set to that JobId, and
  *   finally, it returns the last JobId to the caller.
  *
- * Returns: false on error
- *          true  if OK and jcr->previous_jr filled in
+ * Returns: -1  on error
+ *           0  if no jobs to migrate
+ *           1  if OK and jcr->previous_jr filled in
  */
-static bool get_job_to_migrate(JCR *jcr)
+static int get_job_to_migrate(JCR *jcr)
 {
    char ed1[30];
    POOL_MEM query(PM_MESSAGE);
@@ -561,10 +596,10 @@ static bool get_job_to_migrate(JCR *jcr)
    idpkt ids, mid, jids;
    db_int64_ctx ctx;
    int64_t pool_bytes;
-   bool ok;
    time_t ttime;
    struct tm tm;
    char dt[MAX_TIME_LENGTH];
+   int count = 0;
 
    ids.list = get_pool_memory(PM_MESSAGE);
    ids.list[0] = 0;
@@ -630,7 +665,7 @@ static bool get_job_to_migrate(JCR *jcr)
       case MT_POOL_OCCUPANCY:
          ctx.count = 0;
          /* Find count of bytes in pool */
-         Mmsg(query, sql_pool_bytes, jcr->pool->hdr.name);
+         Mmsg(query, sql_pool_bytes, jcr->rpool->name());
          if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
             Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
             goto bail_out;
@@ -640,9 +675,9 @@ static bool get_job_to_migrate(JCR *jcr)
             goto ok_out;
          }
          pool_bytes = ctx.value;
-         Dmsg2(dbglevel, "highbytes=%d pool=%d\n", (int)jcr->pool->MigrationHighBytes,
+         Dmsg2(dbglevel, "highbytes=%d pool=%d\n", (int)jcr->rpool->MigrationHighBytes,
                (int)pool_bytes);
-         if (pool_bytes < (int64_t)jcr->pool->MigrationHighBytes) {
+         if (pool_bytes < (int64_t)jcr->rpool->MigrationHighBytes) {
             Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
             goto ok_out;
          }
@@ -650,7 +685,7 @@ static bool get_job_to_migrate(JCR *jcr)
 
          ids.count = 0;
          /* Find a list of MediaIds that could be migrated */
-         Mmsg(query, sql_mediaids, jcr->pool->hdr.name);
+         Mmsg(query, sql_mediaids, jcr->rpool->name());
          Dmsg1(dbglevel, "query=%s\n", query.c_str());
          if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
             Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
@@ -678,8 +713,7 @@ static bool get_job_to_migrate(JCR *jcr)
             }
             mid.count = 1;
             Mmsg(mid.list, "%s", edit_int64(MediaId, ed1));
-            ok = find_jobids_from_mediaid_list(jcr, &mid, "Volumes");
-            if (!ok) {
+            if (!find_jobids_from_mediaid_list(jcr, &mid, "Volumes")) {
                continue;
             }
             if (i != 0) {
@@ -698,9 +732,9 @@ static bool get_job_to_migrate(JCR *jcr)
             }
             pool_bytes -= ctx.value;
             Dmsg1(dbglevel, "Job bytes=%d\n", (int)ctx.value);
-            Dmsg2(dbglevel, "lowbytes=%d pool=%d\n", (int)jcr->pool->MigrationLowBytes,
+            Dmsg2(dbglevel, "lowbytes=%d pool=%d\n", (int)jcr->rpool->MigrationLowBytes,
                   (int)pool_bytes);
-            if (pool_bytes <= (int64_t)jcr->pool->MigrationLowBytes) {
+            if (pool_bytes <= (int64_t)jcr->rpool->MigrationLowBytes) {
                Dmsg0(dbglevel, "We should be done.\n");
                break;
             }
@@ -711,12 +745,12 @@ static bool get_job_to_migrate(JCR *jcr)
          break;
 
       case MT_POOL_TIME:
-         ttime = time(NULL) - (time_t)jcr->pool->MigrationTime;
+         ttime = time(NULL) - (time_t)jcr->rpool->MigrationTime;
          (void)localtime_r(&ttime, &tm);
          strftime(dt, sizeof(dt), "%Y-%m-%d %H:%M:%S", &tm);
 
          ids.count = 0;
-         Mmsg(query, sql_pool_time, jcr->pool->hdr.name, dt);
+         Mmsg(query, sql_pool_time, jcr->rpool->name(), dt);
          Dmsg1(dbglevel, "query=%s\n", query.c_str());
          if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
             Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
@@ -760,7 +794,7 @@ static bool get_job_to_migrate(JCR *jcr)
          goto bail_out;
       } else if (stat == 0) {
          Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
-         goto bail_out;
+         goto ok_out;
       }
    }
    
@@ -773,7 +807,7 @@ static bool get_job_to_migrate(JCR *jcr)
       goto bail_out;
    } else if (stat == 0) {
       Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
-      goto bail_out;
+      goto ok_out;
    }
 
    jcr->previous_jr.JobId = JobId;
@@ -790,19 +824,19 @@ static bool get_job_to_migrate(JCR *jcr)
    Dmsg3(dbglevel, "Migration JobId=%d  using JobId=%s Job=%s\n",
       jcr->JobId,
       edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
+   count = 1;
 
 ok_out:
-   ok = true;
    goto out;
 
 bail_out:
-   ok = false;
+   count = -1;
            
 out:
    free_pool_memory(ids.list);
    free_pool_memory(mid.list);
    free_pool_memory(jids.list);
-   return ok;
+   return count;
 }
 
 static void start_migration_job(JCR *jcr)
@@ -831,16 +865,17 @@ static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
 
    ids->count = 0;
    /* Basic query for MediaId */
-   Mmsg(query, query1, jcr->pool->hdr.name);
+   Mmsg(query, query1, jcr->rpool->name());
    if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
       Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
       goto bail_out;
    }
    if (ids->count == 0) {
       Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
-   }
-   if (ids->count != 1) {
-      Jmsg(jcr, M_FATAL, 0, _("SQL logic error. Count should be 1 but is %d\n"), 
+      ok = true;         /* Not an error */
+      goto bail_out;
+   } else if (ids->count != 1) {
+      Jmsg(jcr, M_FATAL, 0, _("SQL error. Expected 1 MediaId got %d\n"), 
          ids->count);
       goto bail_out;
    }
@@ -889,17 +924,9 @@ static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
          type);
       goto bail_out;
    }
-   Dmsg1(dbglevel, "regex=%s\n", jcr->job->selection_pattern);
-   /* Compile regex expression */
-   rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
-   if (rc != 0) {
-      regerror(rc, &preg, prbuf, sizeof(prbuf));
-      Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
-           jcr->job->selection_pattern, prbuf);
-      goto bail_out;
-   }
+   Dmsg1(dbglevel, "regex-sel-pattern=%s\n", jcr->job->selection_pattern);
    /* Basic query for names */
-   Mmsg(query, query1, jcr->pool->hdr.name);
+   Mmsg(query, query1, jcr->rpool->name());
    Dmsg1(dbglevel, "get name query1=%s\n", query.c_str());
    if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler, 
         (void *)item_chain)) {
@@ -907,29 +934,51 @@ static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
            _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
       goto bail_out;
    }
-   /* Now apply the regex to the names and remove any item not matched */
-   foreach_dlist(item, item_chain) {
-      const int nmatch = 30;
-      regmatch_t pmatch[nmatch];
+   Dmsg1(dbglevel, "query1 returned %d names\n", item_chain->size());
+   if (item_chain->size() == 0) {
+      Jmsg(jcr, M_INFO, 0, _("Query of Pool \"%s\" returned no Jobs to migrate.\n"),
+           jcr->rpool->name());
+      ok = true;
+      goto bail_out;               /* skip regex match */
+   } else {
+      /* Compile regex expression */
+      rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
+      if (rc != 0) {
+         regerror(rc, &preg, prbuf, sizeof(prbuf));
+         Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
+              jcr->job->selection_pattern, prbuf);
+         goto bail_out;
+      }
+      /* Now apply the regex to the names and remove any item not matched */
+      foreach_dlist(item, item_chain) {
+         const int nmatch = 30;
+         regmatch_t pmatch[nmatch];
+         if (last_item) {
+            Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
+            free(last_item->item);
+            item_chain->remove(last_item);
+         }
+         Dmsg1(dbglevel, "get name Item=%s\n", item->item);
+         rc = regexec(&preg, item->item, nmatch, pmatch,  0);
+         if (rc == 0) {
+            last_item = NULL;   /* keep this one */
+         } else {   
+            last_item = item;
+         }
+      }
       if (last_item) {
-         Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
          free(last_item->item);
+         Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
          item_chain->remove(last_item);
       }
-      Dmsg1(dbglevel, "get name Item=%s\n", item->item);
-      rc = regexec(&preg, item->item, nmatch, pmatch,  0);
-      if (rc == 0) {
-         last_item = NULL;   /* keep this one */
-      } else {   
-         last_item = item;
-      }
+      regfree(&preg);
    }
-   if (last_item) {
-      free(last_item->item);
-      Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
-      item_chain->remove(last_item);
+   if (item_chain->size() == 0) {
+      Jmsg(jcr, M_INFO, 0, _("Regex pattern matched no Jobs to migrate.\n"));
+      ok = true;
+      goto bail_out;               /* skip regex match */
    }
-   regfree(&preg);
+
    /* 
     * At this point, we have a list of items in item_chain
     *  that have been matched by the regex, so now we need
@@ -938,7 +987,7 @@ static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
    ids->count = 0;
    foreach_dlist(item, item_chain) {
       Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
-      Mmsg(query, query2, item->item, jcr->pool->hdr.name);
+      Mmsg(query, query2, item->item, jcr->rpool->name());
       Dmsg1(dbglevel, "get id from name query2=%s\n", query.c_str());
       if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
          Jmsg(jcr, M_FATAL, 0,
@@ -950,10 +999,13 @@ static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
       Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
    }
    ok = true;
+
 bail_out:
    Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
+   foreach_dlist(item, item_chain) {
+      free(item->item);
+   }
    delete item_chain;
-   Dmsg0(dbglevel, "After delete item_chain\n");
    return ok;
 }
 
@@ -976,10 +1028,8 @@ void migration_cleanup(JCR *jcr, int TermCode)
    POOL_MEM query(PM_MESSAGE);
 
    Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
-   dequeue_messages(jcr);             /* display any queued messages */
+   update_job_end(jcr, TermCode);
    memset(&mr, 0, sizeof(mr));
-   set_jcr_job_status(jcr, TermCode);
-   update_job_end_record(jcr);        /* update database */
 
    /* 
     * Check if we actually did something.  
@@ -993,8 +1043,7 @@ void migration_cleanup(JCR *jcr, int TermCode)
       mig_jcr->jr.RealEndTime = 0; 
       mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
 
-      set_jcr_job_status(mig_jcr, TermCode);
-      update_job_end_record(mig_jcr);
+      update_job_end(mig_jcr, TermCode);
      
       /* Update final items to set them to the previous job's values */
       Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
@@ -1071,8 +1120,14 @@ void migration_cleanup(JCR *jcr, int TermCode)
          break;
       }
   } else {
-     msg_type = M_ERROR;          /* Generate error message */
-     term_msg = _("*** %s Error ***");
+     if (jcr->previous_jr.JobId != 0) {
+        /* Mark previous job as migrated */
+        Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
+             (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
+        Dmsg1(000, "Mark: %s\n", query.c_str());
+        db_sql_query(jcr->db, query.c_str(), NULL, NULL);
+     }
+     term_msg = _("%s -- no files to migrate");
   }
 
    bsnprintf(term_code, sizeof(term_code), term_msg, "Migration");
@@ -1096,8 +1151,9 @@ void migration_cleanup(JCR *jcr, int TermCode)
 "  Backup Level:           %s%s\n"
 "  Client:                 %s\n"
 "  FileSet:                \"%s\" %s\n"
-"  Pool:                   \"%s\" (From %s)\n"
+"  Read Pool:              \"%s\" (From %s)\n"
 "  Read Storage:           \"%s\" (From %s)\n"
+"  Write Pool:             \"%s\" (From %s)\n"
 "  Write Storage:          \"%s\" (From %s)\n"
 "  Start time:             %s\n"
 "  End time:               %s\n"
@@ -1123,9 +1179,10 @@ void migration_cleanup(JCR *jcr, int TermCode)
         level_to_str(jcr->JobLevel), jcr->since,
         jcr->client->name(),
         jcr->fileset->name(), jcr->FSCreateTime,
-        jcr->pool->name(), jcr->pool_source,
+        jcr->rpool->name(), jcr->rpool_source,
         jcr->rstore?jcr->rstore->name():"*None*", 
         NPRT(jcr->rstore_source), 
+        jcr->pool->name(), jcr->pool_source,
         jcr->wstore?jcr->wstore->name():"*None*", 
         NPRT(jcr->wstore_source),
         sdt,
@@ -1186,3 +1243,24 @@ static int get_next_dbid_from_list(char **p, DBId_t *DBId)
    *DBId = str_to_int64(id);
    return 1;
 }
+
+bool set_migration_wstorage(JCR *jcr, POOL *pool)
+{
+   POOL *wpool = pool->NextPool;
+
+   if (!wpool) {
+      Jmsg(jcr, M_FATAL, 0, _("No Next Pool specification found in Pool \"%s\".\n"),
+         pool->hdr.name);
+      return false;
+   }
+
+   if (!wpool->storage || wpool->storage->size() == 0) {
+      Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Next Pool \"%s\".\n"),
+         wpool->name());
+      return false;
+   }
+
+   /* If pool storage specified, use it instead of job storage for backup */
+   copy_wstorage(jcr, wpool->storage, _("Storage from Pool's NextPool resource"));
+   return true;
+}