]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/migrate.c
Implement Catalog in Pool resource. It overrides catalog specified
[bacula/bacula] / bacula / src / dird / migrate.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2004-2007 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of John Walker.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *
30  *   Bacula Director -- migrate.c -- responsible for doing
31  *     migration jobs.
32  *
33  *     Kern Sibbald, September MMIV
34  *
35  *  Basic tasks done here:
36  *     Open DB and create records for this job.
37  *     Open Message Channel with Storage daemon to tell him a job will be starting.
38  *     Open connection with Storage daemon and pass him commands
39  *       to do the backup.
40  *     When the Storage daemon finishes the job, update the DB.
41  *
42  *   Version $Id$
43  */
44
45 #include "bacula.h"
46 #include "dird.h"
47 #include "ua.h"
48 #ifndef HAVE_REGEX_H
49 #include "lib/bregex.h"
50 #else
51 #include <regex.h>
52 #endif
53
54 static const int dbglevel = 10;
55
56 static char OKbootstrap[] = "3000 OK bootstrap\n";
57 static int get_job_to_migrate(JCR *jcr);
58 struct idpkt;
59 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
60                  const char *query2, const char *type);
61 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
62                  const char *type);
63 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type);
64 static void start_migration_job(JCR *jcr);
65 static int get_next_dbid_from_list(char **p, DBId_t *DBId);
66
67 /* 
68  * Called here before the job is run to do the job
69  *   specific setup.  Note, one of the important things to
70  *   complete in this init code is to make the definitive
71  *   choice of input and output storage devices.  This is
72  *   because immediately after the init, the job is queued
73  *   in the jobq.c code, and it checks that all the resources
74  *   (storage resources in particular) are available, so these
75  *   must all be properly defined.
76  *
77  *  previous_jr refers to the job DB record of the Job that is
78  *    going to be migrated.
79  *  prev_job refers to the job resource of the Job that is
80  *    going to be migrated.
81  *  jcr is the jcr for the current "migration" job.  It is a
82  *    control job that is put in the DB as a migration job, which
83  *    means that this job migrated a previous job to a new job.
84  *    No Volume or File data is associated with this control
85  *    job.
86  *  mig_jcr refers to the newly migrated job that is run by
87  *    the current jcr.  It is a backup job that moves (migrates) the
88  *    data written for the previous_jr into the new pool.  This
89  *    job (mig_jcr) becomes the new backup job that replaces
90  *    the original backup job. Note, this jcr is not really run. It
91  *    is simply attached to the current jcr.  It will show up in
92  *    the Director's status output, but not in the SD or FD, both of
93  *    which deal only with the current migration job (i.e. jcr).
94  */
95 bool do_migration_init(JCR *jcr)
96 {
97    POOL_DBR pr;
98    POOL *pool;
99    char ed1[100];
100    JOB *job, *prev_job;
101    JCR *mig_jcr;                   /* newly migrated job */
102    int count;
103
104
105    apply_pool_overrides(jcr);
106
107    jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->name());
108    if (jcr->jr.PoolId == 0) {
109       Dmsg1(dbglevel, "JobId=%d no PoolId\n", (int)jcr->JobId);
110       Jmsg(jcr, M_FATAL, 0, _("Could not get or create a Pool record.\n"));
111       return false;
112    }
113    /*
114     * Note, at this point, pool is the pool for this job.  We
115     *  transfer it to rpool (read pool), and a bit later,
116     *  pool will be changed to point to the write pool, 
117     *  which comes from pool->NextPool.
118     */
119    jcr->rpool = jcr->pool;            /* save read pool */
120    pm_strcpy(jcr->rpool_source, jcr->pool_source);
121
122
123    Dmsg2(dbglevel, "Read pool=%s (From %s)\n", jcr->rpool->name(), jcr->rpool_source);
124
125    /* If we find a job or jobs to migrate it is previous_jr.JobId */
126    count = get_job_to_migrate(jcr);
127    if (count < 0) {
128       return false;
129    }
130    if (count == 0) {
131       return true;
132    }
133
134    Dmsg1(dbglevel, "Back from get_job_to_migrate JobId=%d\n", (int)jcr->JobId);
135
136    if (jcr->previous_jr.JobId == 0) {
137       Dmsg1(dbglevel, "JobId=%d no previous JobId\n", (int)jcr->JobId);
138       Jmsg(jcr, M_INFO, 0, _("No previous Job found to migrate.\n"));
139       return true;                    /* no work */
140    }
141
142    if (!get_or_create_fileset_record(jcr)) {
143       Dmsg1(dbglevel, "JobId=%d no FileSet\n", (int)jcr->JobId);
144       Jmsg(jcr, M_FATAL, 0, _("Could not get or create the FileSet record.\n"));
145       return false;
146    }
147
148    create_restore_bootstrap_file(jcr);
149
150    if (jcr->previous_jr.JobId == 0 || jcr->ExpectedFiles == 0) {
151       set_jcr_job_status(jcr, JS_Terminated);
152       Dmsg1(dbglevel, "JobId=%d expected files == 0\n", (int)jcr->JobId);
153       if (jcr->previous_jr.JobId == 0) {
154          Jmsg(jcr, M_INFO, 0, _("No previous Job found to migrate.\n"));
155       } else {
156          Jmsg(jcr, M_INFO, 0, _("Previous Job has no data to migrate.\n"));
157       }
158       return true;                    /* no work */
159    }
160
161    Dmsg5(dbglevel, "JobId=%d: Previous: Name=%s JobId=%d Type=%c Level=%c\n",
162       (int)jcr->JobId,
163       jcr->previous_jr.Name, (int)jcr->previous_jr.JobId, 
164       jcr->previous_jr.JobType, jcr->previous_jr.JobLevel);
165
166    Dmsg5(dbglevel, "JobId=%d: Current: Name=%s JobId=%d Type=%c Level=%c\n",
167       (int)jcr->JobId,
168       jcr->jr.Name, (int)jcr->jr.JobId, 
169       jcr->jr.JobType, jcr->jr.JobLevel);
170
171    LockRes();
172    job = (JOB *)GetResWithName(R_JOB, jcr->jr.Name);
173    prev_job = (JOB *)GetResWithName(R_JOB, jcr->previous_jr.Name);
174    UnlockRes();
175    if (!job) {
176       Jmsg(jcr, M_FATAL, 0, _("Job resource not found for \"%s\".\n"), jcr->jr.Name);
177       return false;
178    }
179    if (!prev_job) {
180       Jmsg(jcr, M_FATAL, 0, _("Previous Job resource not found for \"%s\".\n"), 
181            jcr->previous_jr.Name);
182       return false;
183    }
184
185    jcr->spool_data = job->spool_data;     /* turn on spooling if requested in job */ 
186
187    /* Create a migation jcr */
188    mig_jcr = jcr->mig_jcr = new_jcr(sizeof(JCR), dird_free_jcr);
189    memcpy(&mig_jcr->previous_jr, &jcr->previous_jr, sizeof(mig_jcr->previous_jr));
190
191    /*
192     * Turn the mig_jcr into a "real" job that takes on the aspects of
193     *   the previous backup job "prev_job".
194     */
195    set_jcr_defaults(mig_jcr, prev_job);
196    if (!setup_job(mig_jcr)) {
197       Jmsg(jcr, M_FATAL, 0, _("setup job failed.\n"));
198       return false;
199    }
200
201    /* Now reset the job record from the previous job */
202    memcpy(&mig_jcr->jr, &jcr->previous_jr, sizeof(mig_jcr->jr));
203    /* Update the jr to reflect the new values of PoolId and JobId. */
204    mig_jcr->jr.PoolId = jcr->jr.PoolId;
205    mig_jcr->jr.JobId = mig_jcr->JobId;
206
207    Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
208       mig_jcr->jr.Name, (int)mig_jcr->jr.JobId, 
209       mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
210
211    /*
212     * Get the PoolId used with the original job. Then
213     *  find the pool name from the database record.
214     */
215    memset(&pr, 0, sizeof(pr));
216    pr.PoolId = mig_jcr->previous_jr.PoolId;
217    if (!db_get_pool_record(jcr, jcr->db, &pr)) {
218       Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
219             edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
220          return false;
221    }
222    /* Get the pool resource corresponding to the original job */
223    pool = (POOL *)GetResWithName(R_POOL, pr.Name);
224    if (!pool) {
225       Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
226       return false;
227    }
228
229    /* If pool storage specified, use it for restore */
230    copy_rstorage(mig_jcr, pool->storage, _("Pool resource"));
231    copy_rstorage(jcr, pool->storage, _("Pool resource"));
232
233    /*
234     * If the original backup pool has a NextPool, make sure a 
235     *  record exists in the database. Note, in this case, we
236     *  will be migrating from pool to pool->NextPool.
237     */
238    if (pool->NextPool) {
239       jcr->jr.PoolId = get_or_create_pool_record(jcr, pool->NextPool->name());
240       if (jcr->jr.PoolId == 0) {
241          return false;
242       }
243    }
244    if (!set_migration_wstorage(jcr, pool)) {
245       return false;
246    }
247    mig_jcr->pool = jcr->pool = pool->NextPool;
248    pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
249    mig_jcr->jr.PoolId = jcr->jr.PoolId;
250
251    Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
252    return true;
253 }
254
255 /*
256  * Do a Migration of a previous job
257  *
258  *  Returns:  false on failure
259  *            true  on success
260  */
261 bool do_migration(JCR *jcr)
262 {
263    char ed1[100];
264    BSOCK *sd;
265    JCR *mig_jcr = jcr->mig_jcr;    /* newly migrated job */
266
267    /*
268     * If mig_jcr is NULL, there is nothing to do for this job,
269     *  so set a normal status, cleanup and return OK.
270     */
271    if (!mig_jcr) {
272       set_jcr_job_status(jcr, JS_Terminated);
273       migration_cleanup(jcr, jcr->JobStatus);
274       return true;
275    }
276
277    /* Print Job Start message */
278    Jmsg(jcr, M_INFO, 0, _("Start Migration JobId %s, Job=%s\n"),
279         edit_uint64(jcr->JobId, ed1), jcr->Job);
280
281
282
283    /*
284     * Open a message channel connection with the Storage
285     * daemon. This is to let him know that our client
286     * will be contacting him for a backup  session.
287     *
288     */
289    Dmsg0(110, "Open connection with storage daemon\n");
290    set_jcr_job_status(jcr, JS_WaitSD);
291    set_jcr_job_status(mig_jcr, JS_WaitSD);
292    /*
293     * Start conversation with Storage daemon
294     */
295    if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
296       return false;
297    }
298    sd = jcr->store_bsock;
299    /*
300     * Now start a job with the Storage daemon
301     */
302    Dmsg2(dbglevel, "Read store=%s, write store=%s\n", 
303       ((STORE *)jcr->rstorage->first())->name(),
304       ((STORE *)jcr->wstorage->first())->name());
305    if (((STORE *)jcr->rstorage->first())->name() == ((STORE *)jcr->wstorage->first())->name()) {
306       Jmsg(jcr, M_FATAL, 0, _("Read storage \"%s\" same as write storage.\n"),
307            ((STORE *)jcr->rstorage->first())->name());
308       return false;
309    }
310    if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage)) {
311       return false;
312    }
313    Dmsg0(150, "Storage daemon connection OK\n");
314
315    if (!send_bootstrap_file(jcr, sd) ||
316        !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
317       return false;
318    }
319
320    /*    
321     * We re-update the job start record so that the start
322     *  time is set after the run before job.  This avoids 
323     *  that any files created by the run before job will
324     *  be saved twice.  They will be backed up in the current
325     *  job, but not in the next one unless they are changed.
326     *  Without this, they will be backed up in this job and
327     *  in the next job run because in that case, their date 
328     *   is after the start of this run.
329     */
330    jcr->start_time = time(NULL);
331    jcr->jr.StartTime = jcr->start_time;
332    jcr->jr.JobTDate = jcr->start_time;
333    set_jcr_job_status(jcr, JS_Running);
334
335    /* Update job start record for this migration control job */
336    if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
337       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
338       return false;
339    }
340
341
342    mig_jcr->start_time = time(NULL);
343    mig_jcr->jr.StartTime = mig_jcr->start_time;
344    mig_jcr->jr.JobTDate = mig_jcr->start_time;
345    set_jcr_job_status(mig_jcr, JS_Running);
346
347    /* Update job start record for the real migration backup job */
348    if (!db_update_job_start_record(mig_jcr, mig_jcr->db, &mig_jcr->jr)) {
349       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(mig_jcr->db));
350       return false;
351    }
352
353    Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
354       mig_jcr->jr.Name, (int)mig_jcr->jr.JobId, 
355       mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
356
357
358    /*
359     * Start the job prior to starting the message thread below
360     * to avoid two threads from using the BSOCK structure at
361     * the same time.
362     */
363    if (!bnet_fsend(sd, "run")) {
364       return false;
365    }
366
367    /*
368     * Now start a Storage daemon message thread
369     */
370    if (!start_storage_daemon_message_thread(jcr)) {
371       return false;
372    }
373
374
375    set_jcr_job_status(jcr, JS_Running);
376    set_jcr_job_status(mig_jcr, JS_Running);
377
378    /* Pickup Job termination data */
379    /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
380    wait_for_storage_daemon_termination(jcr);
381    set_jcr_job_status(jcr, jcr->SDJobStatus);
382    db_write_batch_file_records(jcr);    /* used by bulk batch file insert */
383    if (jcr->JobStatus != JS_Terminated) {
384       return false;
385    }
386
387    migration_cleanup(jcr, jcr->JobStatus);
388    if (mig_jcr) {
389       char jobid[50];
390       UAContext *ua = new_ua_context(jcr);
391       edit_uint64(jcr->previous_jr.JobId, jobid);
392       /* Purge all old file records, but leave Job record */
393       purge_files_from_jobs(ua, jobid);
394       free_ua_context(ua);
395    }
396    return true;
397 }
398
399 struct idpkt {
400    POOLMEM *list;
401    uint32_t count;
402 };
403
404 /* Add an item to the list if it is unique */
405 static void add_unique_id(idpkt *ids, char *item) 
406 {
407    char id[30];
408    char *q = ids->list;
409
410    /* Walk through current list to see if each item is the same as item */
411    for ( ; *q; ) {
412        id[0] = 0;
413        for (int i=0; i<(int)sizeof(id); i++) {
414           if (*q == 0) {
415              break;
416           } else if (*q == ',') {
417              q++;
418              break;
419           }
420           id[i] = *q++;
421           id[i+1] = 0;
422        }
423        if (strcmp(item, id) == 0) {
424           return;
425        }
426    }
427    /* Did not find item, so add it to list */
428    if (ids->count == 0) {
429       ids->list[0] = 0;
430    } else {
431       pm_strcat(ids->list, ",");
432    }
433    pm_strcat(ids->list, item);
434    ids->count++;
435 // Dmsg3(0, "add_uniq count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
436    return;
437 }
438
439 /*
440  * Callback handler make list of DB Ids
441  */
442 static int unique_dbid_handler(void *ctx, int num_fields, char **row)
443 {
444    idpkt *ids = (idpkt *)ctx;
445
446    add_unique_id(ids, row[0]);
447    Dmsg3(dbglevel, "dbid_hdlr count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
448    return 0;
449 }
450
451
452 struct uitem {
453    dlink link;   
454    char *item;
455 };
456
457 static int item_compare(void *item1, void *item2)
458 {
459    uitem *i1 = (uitem *)item1;
460    uitem *i2 = (uitem *)item2;
461    return strcmp(i1->item, i2->item);
462 }
463
464 static int unique_name_handler(void *ctx, int num_fields, char **row)
465 {
466    dlist *list = (dlist *)ctx;
467
468    uitem *new_item = (uitem *)malloc(sizeof(uitem));
469    uitem *item;
470    
471    memset(new_item, 0, sizeof(uitem));
472    new_item->item = bstrdup(row[0]);
473    Dmsg1(dbglevel, "Unique_name_hdlr Item=%s\n", row[0]);
474    item = (uitem *)list->binary_insert((void *)new_item, item_compare);
475    if (item != new_item) {            /* already in list */
476       free(new_item->item);
477       free((char *)new_item);
478       return 0;
479    }
480    return 0;
481 }
482
483 /* Get Job names in Pool */
484 const char *sql_job =
485    "SELECT DISTINCT Job.Name from Job,Pool"
486    " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
487
488 /* Get JobIds from regex'ed Job names */
489 const char *sql_jobids_from_job =
490    "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
491    " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
492    " ORDER by Job.StartTime";
493
494 /* Get Client names in Pool */
495 const char *sql_client =
496    "SELECT DISTINCT Client.Name from Client,Pool,Job"
497    " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
498    " Job.PoolId=Pool.PoolId";
499
500 /* Get JobIds from regex'ed Client names */
501 const char *sql_jobids_from_client =
502    "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
503    " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
504    " AND Job.ClientId=Client.ClientId AND Job.Type='B'"
505    " ORDER by Job.StartTime";
506
507 /* Get Volume names in Pool */
508 const char *sql_vol = 
509    "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
510    " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
511    " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
512
513 /* Get JobIds from regex'ed Volume names */
514 const char *sql_jobids_from_vol =
515    "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
516    " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
517    " AND JobMedia.JobId=Job.JobId AND Job.Type='B'"
518    " ORDER by Job.StartTime";
519
520
521 const char *sql_smallest_vol = 
522    "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
523    " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
524    " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
525    " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
526    " ORDER BY VolBytes ASC LIMIT 1";
527
528 const char *sql_oldest_vol = 
529    "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
530    " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
531    " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
532    " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
533    " ORDER BY LastWritten ASC LIMIT 1";
534
535 /* Get JobIds when we have selected MediaId */
536 const char *sql_jobids_from_mediaid =
537    "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
538    " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId=%s"
539    " AND Job.Type='B'"
540    " ORDER by Job.StartTime";
541
542 /* Get tne number of bytes in the pool */
543 const char *sql_pool_bytes =
544    "SELECT SUM(VolBytes) FROM Media,Pool WHERE"
545    " VolStatus in ('Full','Used','Error','Append') AND Media.Enabled=1 AND"
546    " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
547
548 /* Get tne number of bytes in the Jobs */
549 const char *sql_job_bytes =
550    "SELECT SUM(JobBytes) FROM Job WHERE JobId IN (%s)";
551
552
553 /* Get Media Ids in Pool */
554 const char *sql_mediaids =
555    "SELECT MediaId FROM Media,Pool WHERE"
556    " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
557    " Media.PoolId=Pool.PoolId AND Pool.Name='%s' ORDER BY LastWritten ASC";
558
559 /* Get JobIds in Pool longer than specified time */
560 const char *sql_pool_time = 
561    "SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
562    " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
563    " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
564    " Job.Type='B' AND"
565    " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
566    " AND Job.RealEndTime<='%s'";
567
568 /*
569 * const char *sql_ujobid =
570 *   "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
571 *   " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
572 *   " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
573 */
574
575
576
577 /*
578  *
579  * This is the central piece of code that finds a job or jobs 
580  *   actually JobIds to migrate.  It first looks to see if one
581  *   has been "manually" specified in jcr->MigrateJobId, and if
582  *   so, it returns that JobId to be run.  Otherwise, it
583  *   examines the Selection Type to see what kind of migration
584  *   we are doing (Volume, Job, Client, ...) and applies any
585  *   Selection Pattern if appropriate to obtain a list of JobIds.
586  *   Finally, it will loop over all the JobIds found, except the last
587  *   one starting a new job with MigrationJobId set to that JobId, and
588  *   finally, it returns the last JobId to the caller.
589  *
590  * Returns: -1  on error
591  *           0  if no jobs to migrate
592  *           1  if OK and jcr->previous_jr filled in
593  */
594 static int get_job_to_migrate(JCR *jcr)
595 {
596    char ed1[30], ed2[30];
597    POOL_MEM query(PM_MESSAGE);
598    JobId_t JobId;
599    DBId_t DBId = 0;
600    int stat;
601    char *p;
602    idpkt ids, mid, jids;
603    db_int64_ctx ctx;
604    int64_t pool_bytes;
605    time_t ttime;
606    struct tm tm;
607    char dt[MAX_TIME_LENGTH];
608    int count = 0;
609
610    ids.list = get_pool_memory(PM_MESSAGE);
611    ids.list[0] = 0;
612    ids.count = 0;
613    mid.list = get_pool_memory(PM_MESSAGE);
614    mid.list[0] = 0;
615    mid.count = 0;
616    jids.list = get_pool_memory(PM_MESSAGE);
617    jids.list[0] = 0;
618    jids.count = 0;
619
620
621    /*
622     * If MigrateJobId is set, then we migrate only that Job,
623     *  otherwise, we go through the full selection of jobs to
624     *  migrate.
625     */
626    if (jcr->MigrateJobId != 0) {
627       Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
628       edit_uint64(jcr->MigrateJobId, ids.list);
629       ids.count = 1;
630    } else {
631       switch (jcr->job->selection_type) {
632       case MT_JOB:
633          if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
634             goto bail_out;
635          } 
636          break;
637       case MT_CLIENT:
638          if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
639             goto bail_out;
640          } 
641          break;
642       case MT_VOLUME:
643          if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
644             goto bail_out;
645          } 
646          break;
647       case MT_SQLQUERY:
648          if (!jcr->job->selection_pattern) {
649             Jmsg(jcr, M_FATAL, 0, _("No Migration SQL selection pattern specified.\n"));
650             goto bail_out;
651          }
652          Dmsg1(dbglevel, "SQL=%s\n", jcr->job->selection_pattern);
653          if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
654               unique_dbid_handler, (void *)&ids)) {
655             Jmsg(jcr, M_FATAL, 0,
656                  _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
657             goto bail_out;
658          }
659          break;
660       case MT_SMALLEST_VOL:
661          if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
662             goto bail_out;
663          }
664          break;
665       case MT_OLDEST_VOL:
666          if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
667             goto bail_out;
668          }
669          break;
670
671       case MT_POOL_OCCUPANCY:
672          ctx.count = 0;
673          /* Find count of bytes in pool */
674          Mmsg(query, sql_pool_bytes, jcr->rpool->name());
675          if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
676             Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
677             goto bail_out;
678          }
679          if (ctx.count == 0) {
680             Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
681             goto ok_out;
682          }
683          pool_bytes = ctx.value;
684          Dmsg2(dbglevel, "highbytes=%lld pool=%lld\n", jcr->rpool->MigrationHighBytes,
685                pool_bytes);
686          if (pool_bytes < (int64_t)jcr->rpool->MigrationHighBytes) {
687             Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
688             goto ok_out;
689          }
690          Dmsg0(dbglevel, "We should do Occupation migration.\n");
691
692          ids.count = 0;
693          /* Find a list of MediaIds that could be migrated */
694          Mmsg(query, sql_mediaids, jcr->rpool->name());
695          Dmsg1(dbglevel, "query=%s\n", query.c_str());
696          if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
697             Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
698             goto bail_out;
699          }
700          if (ids.count == 0) {
701             Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
702             goto ok_out;
703          }
704          Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);
705
706          if (!find_jobids_from_mediaid_list(jcr, &ids, "Volumes")) {
707             goto bail_out;
708          }
709          /* ids == list of jobs  */
710          p = ids.list;
711          for (int i=0; i < (int)ids.count; i++) {
712             stat = get_next_dbid_from_list(&p, &DBId);
713             Dmsg2(dbglevel, "get_next_dbid stat=%d JobId=%u\n", stat, (uint32_t)DBId);
714             if (stat < 0) {
715                Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
716                goto bail_out;
717             } else if (stat == 0) {
718                break;
719             }
720
721             mid.count = 1;
722             Mmsg(mid.list, "%s", edit_int64(DBId, ed1));
723             if (jids.count > 0) {
724                pm_strcat(jids.list, ",");
725             }
726             pm_strcat(jids.list, mid.list);
727             jids.count += mid.count;
728
729             /* Find count of bytes from Jobs */
730             Mmsg(query, sql_job_bytes, mid.list);
731             Dmsg1(dbglevel, "Jobbytes query: %s\n", query.c_str());
732             if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
733                Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
734                goto bail_out;
735             }
736             pool_bytes -= ctx.value;
737             Dmsg1(dbglevel, "Total migrate Job bytes=%s\n", edit_int64(ctx.value, ed1));
738             Dmsg2(dbglevel, "lowbytes=%s poolafter=%s\n", 
739                   edit_int64(jcr->rpool->MigrationLowBytes, ed1),
740                   edit_int64(pool_bytes, ed2));
741             if (pool_bytes <= (int64_t)jcr->rpool->MigrationLowBytes) {
742                Dmsg0(dbglevel, "We should be done.\n");
743                break;
744             }
745          }
746          /* Transfer jids to ids, where the jobs list is expected */
747          ids.count = jids.count;
748          pm_strcpy(ids.list, jids.list);
749          Dmsg2(dbglevel, "Pool Occupancy ids=%d JobIds=%s\n", ids.count, ids.list);
750          break;
751
752       case MT_POOL_TIME:
753          ttime = time(NULL) - (time_t)jcr->rpool->MigrationTime;
754          (void)localtime_r(&ttime, &tm);
755          strftime(dt, sizeof(dt), "%Y-%m-%d %H:%M:%S", &tm);
756
757          ids.count = 0;
758          Mmsg(query, sql_pool_time, jcr->rpool->name(), dt);
759          Dmsg1(dbglevel, "query=%s\n", query.c_str());
760          if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
761             Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
762             goto bail_out;
763          }
764          if (ids.count == 0) {
765             Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
766             goto ok_out;
767          }
768          Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
769          break;
770
771       default:
772          Jmsg(jcr, M_FATAL, 0, _("Unknown Migration Selection Type.\n"));
773          goto bail_out;
774       }
775    }
776
777    /*
778     * Loop over all jobids except the last one, sending
779     *  them to start_migration_job(), which will start a job
780     *  for each of them.  For the last JobId, we handle it below.
781     */
782    p = ids.list;
783    if (ids.count == 0) {
784       Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
785       goto ok_out;
786    }
787
788    Jmsg(jcr, M_INFO, 0, _("The following %u JobId%s were chosen to be migrated: %s\n"),
789       ids.count, ids.count==0?"":"s", ids.list);
790
791    Dmsg2(dbglevel, "Before loop count=%d ids=%s\n", ids.count, ids.list);
792    for (int i=1; i < (int)ids.count; i++) {
793       JobId = 0;
794       stat = get_next_jobid_from_list(&p, &JobId);
795       Dmsg3(dbglevel, "get_jobid_no=%d stat=%d JobId=%u\n", i, stat, JobId);
796       jcr->MigrateJobId = JobId;
797       start_migration_job(jcr);
798       Dmsg0(dbglevel, "Back from start_migration_job\n");
799       if (stat < 0) {
800          Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
801          goto bail_out;
802       } else if (stat == 0) {
803          Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
804          goto ok_out;
805       }
806    }
807    
808    /* Now get the last JobId and handle it in the current job */
809    JobId = 0;
810    stat = get_next_jobid_from_list(&p, &JobId);
811    Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, (int)JobId);
812    if (stat < 0) {
813       Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
814       goto bail_out;
815    } else if (stat == 0) {
816       Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
817       goto ok_out;
818    }
819
820    jcr->previous_jr.JobId = JobId;
821    Dmsg1(dbglevel, "Previous jobid=%d\n", (int)jcr->previous_jr.JobId);
822
823    if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
824       Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to migrate. ERR=%s"),
825            edit_int64(jcr->previous_jr.JobId, ed1),
826            db_strerror(jcr->db));
827       goto bail_out;
828    }
829    Jmsg(jcr, M_INFO, 0, _("Migration using JobId=%s Job=%s\n"),
830       edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
831    Dmsg3(dbglevel, "Migration JobId=%d  using JobId=%s Job=%s\n",
832       jcr->JobId,
833       edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
834    count = 1;
835
836 ok_out:
837    goto out;
838
839 bail_out:
840    count = -1;
841            
842 out:
843    free_pool_memory(ids.list);
844    free_pool_memory(mid.list);
845    free_pool_memory(jids.list);
846    return count;
847 }
848
849 static void start_migration_job(JCR *jcr)
850 {
851    UAContext *ua = new_ua_context(jcr);
852    char ed1[50];
853    ua->batch = true;
854    Mmsg(ua->cmd, "run %s jobid=%s", jcr->job->hdr.name, 
855         edit_uint64(jcr->MigrateJobId, ed1));
856    Dmsg1(dbglevel, "=============== Migration cmd=%s\n", ua->cmd);
857    parse_ua_args(ua);                 /* parse command */
858    JobId_t jobid = run_cmd(ua, ua->cmd);
859    if (jobid == 0) {
860       Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
861    } else {
862       Jmsg(jcr, M_INFO, 0, _("Migration JobId %d started.\n"), (int)jobid);
863    }
864    free_ua_context(ua);
865 }
866
867 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
868                  const char *type) 
869 {
870    bool ok = false;
871    POOL_MEM query(PM_MESSAGE);
872
873    ids->count = 0;
874    /* Basic query for MediaId */
875    Mmsg(query, query1, jcr->rpool->name());
876    if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
877       Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
878       goto bail_out;
879    }
880    if (ids->count == 0) {
881       Jmsg(jcr, M_INFO, 0, _("No %s found to migrate.\n"), type);
882       ok = true;         /* Not an error */
883       goto bail_out;
884    } else if (ids->count != 1) {
885       Jmsg(jcr, M_FATAL, 0, _("SQL error. Expected 1 MediaId got %d\n"), ids->count);
886       goto bail_out;
887    }
888    Dmsg2(dbglevel, "%s MediaIds=%s\n", type, ids->list);
889
890    ok = find_jobids_from_mediaid_list(jcr, ids, type);
891
892 bail_out:
893    return ok;
894 }
895
896 /* 
897  * This routine returns:
898  *    false       if an error occurred
899  *    true        otherwise
900  *    ids.count   number of jobids found (may be zero)
901  */       
902 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type) 
903 {
904    bool ok = false;
905    POOL_MEM query(PM_MESSAGE);
906
907    Mmsg(query, sql_jobids_from_mediaid, ids->list);
908    ids->count = 0;
909    if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
910       Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
911       goto bail_out;
912    }
913    if (ids->count == 0) {
914       Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
915    }
916    ok = true;
917
918 bail_out:
919    return ok;
920 }
921
922 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
923                  const char *query2, const char *type) 
924 {
925    dlist *item_chain;
926    uitem *item = NULL;
927    uitem *last_item = NULL;
928    regex_t preg;
929    char prbuf[500];
930    int rc;
931    bool ok = false;
932    POOL_MEM query(PM_MESSAGE);
933
934    item_chain = New(dlist(item, &item->link));
935    if (!jcr->job->selection_pattern) {
936       Jmsg(jcr, M_FATAL, 0, _("No Migration %s selection pattern specified.\n"),
937          type);
938       goto bail_out;
939    }
940    Dmsg1(dbglevel, "regex-sel-pattern=%s\n", jcr->job->selection_pattern);
941    /* Basic query for names */
942    Mmsg(query, query1, jcr->rpool->name());
943    Dmsg1(dbglevel, "get name query1=%s\n", query.c_str());
944    if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler, 
945         (void *)item_chain)) {
946       Jmsg(jcr, M_FATAL, 0,
947            _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
948       goto bail_out;
949    }
950    Dmsg1(dbglevel, "query1 returned %d names\n", item_chain->size());
951    if (item_chain->size() == 0) {
952       Jmsg(jcr, M_INFO, 0, _("Query of Pool \"%s\" returned no Jobs to migrate.\n"),
953            jcr->rpool->name());
954       ok = true;
955       goto bail_out;               /* skip regex match */
956    } else {
957       /* Compile regex expression */
958       rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
959       if (rc != 0) {
960          regerror(rc, &preg, prbuf, sizeof(prbuf));
961          Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
962               jcr->job->selection_pattern, prbuf);
963          goto bail_out;
964       }
965       /* Now apply the regex to the names and remove any item not matched */
966       foreach_dlist(item, item_chain) {
967          const int nmatch = 30;
968          regmatch_t pmatch[nmatch];
969          if (last_item) {
970             Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
971             free(last_item->item);
972             item_chain->remove(last_item);
973          }
974          Dmsg1(dbglevel, "get name Item=%s\n", item->item);
975          rc = regexec(&preg, item->item, nmatch, pmatch,  0);
976          if (rc == 0) {
977             last_item = NULL;   /* keep this one */
978          } else {   
979             last_item = item;
980          }
981       }
982       if (last_item) {
983          free(last_item->item);
984          Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
985          item_chain->remove(last_item);
986       }
987       regfree(&preg);
988    }
989    if (item_chain->size() == 0) {
990       Jmsg(jcr, M_INFO, 0, _("Regex pattern matched no Jobs to migrate.\n"));
991       ok = true;
992       goto bail_out;               /* skip regex match */
993    }
994
995    /* 
996     * At this point, we have a list of items in item_chain
997     *  that have been matched by the regex, so now we need
998     *  to look up their jobids.
999     */
1000    ids->count = 0;
1001    foreach_dlist(item, item_chain) {
1002       Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
1003       Mmsg(query, query2, item->item, jcr->rpool->name());
1004       Dmsg1(dbglevel, "get id from name query2=%s\n", query.c_str());
1005       if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1006          Jmsg(jcr, M_FATAL, 0,
1007               _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
1008          goto bail_out;
1009       }
1010    }
1011    if (ids->count == 0) {
1012       Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
1013    }
1014    ok = true;
1015
1016 bail_out:
1017    Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
1018    foreach_dlist(item, item_chain) {
1019       free(item->item);
1020    }
1021    delete item_chain;
1022    return ok;
1023 }
1024
1025
1026 /*
1027  * Release resources allocated during backup.
1028  */
1029 void migration_cleanup(JCR *jcr, int TermCode)
1030 {
1031    char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
1032    char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
1033    char ec6[50], ec7[50], ec8[50];
1034    char term_code[100], sd_term_msg[100];
1035    const char *term_msg;
1036    int msg_type = M_INFO;
1037    MEDIA_DBR mr;
1038    double kbps;
1039    utime_t RunTime;
1040    JCR *mig_jcr = jcr->mig_jcr;
1041    POOL_MEM query(PM_MESSAGE);
1042
1043    Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
1044    update_job_end(jcr, TermCode);
1045    memset(&mr, 0, sizeof(mr));
1046
1047    /* 
1048     * Check if we actually did something.  
1049     *  mig_jcr is jcr of the newly migrated job.
1050     */
1051    if (mig_jcr) {
1052       mig_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
1053       mig_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
1054       mig_jcr->VolSessionId = jcr->VolSessionId;
1055       mig_jcr->VolSessionTime = jcr->VolSessionTime;
1056       mig_jcr->jr.RealEndTime = 0; 
1057       mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
1058
1059       update_job_end(mig_jcr, TermCode);
1060      
1061       /* Update final items to set them to the previous job's values */
1062       Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
1063                   "JobTDate=%s WHERE JobId=%s", 
1064          jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime, 
1065          edit_uint64(jcr->previous_jr.JobTDate, ec1),
1066          edit_uint64(mig_jcr->jr.JobId, ec2));
1067       db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1068
1069       /* Now mark the previous job as migrated if it terminated normally */
1070       if (jcr->JobStatus == JS_Terminated) {
1071          Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1072               (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
1073          db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1074       } 
1075
1076       if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
1077          Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
1078             db_strerror(jcr->db));
1079          set_jcr_job_status(jcr, JS_ErrorTerminated);
1080       }
1081
1082
1083       update_bootstrap_file(mig_jcr);
1084
1085       if (!db_get_job_volume_names(mig_jcr, mig_jcr->db, mig_jcr->jr.JobId, &mig_jcr->VolumeName)) {
1086          /*
1087           * Note, if the job has failed, most likely it did not write any
1088           *  tape, so suppress this "error" message since in that case
1089           *  it is normal.  Or look at it the other way, only for a
1090           *  normal exit should we complain about this error.
1091           */
1092          if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
1093             Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(mig_jcr->db));
1094          }
1095          mig_jcr->VolumeName[0] = 0;         /* none */
1096       }
1097
1098       if (mig_jcr->VolumeName[0]) {
1099          /* Find last volume name. Multiple vols are separated by | */
1100          char *p = strrchr(mig_jcr->VolumeName, '|');
1101          if (p) {
1102             p++;                         /* skip | */
1103          } else {
1104             p = mig_jcr->VolumeName;     /* no |, take full name */
1105          }
1106          bstrncpy(mr.VolumeName, p, sizeof(mr.VolumeName));
1107          if (!db_get_media_record(jcr, jcr->db, &mr)) {
1108             Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
1109                mr.VolumeName, db_strerror(jcr->db));
1110          }
1111       }
1112
1113       switch (jcr->JobStatus) {
1114       case JS_Terminated:
1115          if (jcr->Errors || jcr->SDErrors) {
1116             term_msg = _("%s OK -- with warnings");
1117          } else {
1118             term_msg = _("%s OK");
1119          }
1120          break;
1121       case JS_FatalError:
1122       case JS_ErrorTerminated:
1123          term_msg = _("*** %s Error ***");
1124          msg_type = M_ERROR;          /* Generate error message */
1125          if (jcr->store_bsock) {
1126             bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1127             if (jcr->SD_msg_chan) {
1128                pthread_cancel(jcr->SD_msg_chan);
1129             }
1130          }
1131          break;
1132       case JS_Canceled:
1133          term_msg = _("%s Canceled");
1134          if (jcr->store_bsock) {
1135             bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1136             if (jcr->SD_msg_chan) {
1137                pthread_cancel(jcr->SD_msg_chan);
1138             }
1139          }
1140          break;
1141       default:
1142          term_msg = _("Inappropriate %s term code");
1143          break;
1144       }
1145   } else {
1146      if (jcr->previous_jr.JobId != 0) {
1147         /* Mark previous job as migrated */
1148         Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1149              (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
1150         db_sql_query(jcr->db, query.c_str(), NULL, NULL);
1151      }
1152      term_msg = _("%s -- no files to migrate");
1153   }
1154
1155    bsnprintf(term_code, sizeof(term_code), term_msg, "Migration");
1156    bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
1157    bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
1158    RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
1159    if (RunTime <= 0) {
1160       kbps = 0;
1161    } else {
1162       kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
1163    }
1164
1165
1166    jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
1167
1168    Jmsg(jcr, msg_type, 0, _("Bacula %s %s (%s): %s\n"
1169 "  Build OS:               %s %s %s\n"
1170 "  Prev Backup JobId:      %s\n"
1171 "  New Backup JobId:       %s\n"
1172 "  Migration JobId:        %s\n"
1173 "  Migration Job:          %s\n"
1174 "  Backup Level:           %s%s\n"
1175 "  Client:                 %s\n"
1176 "  FileSet:                \"%s\" %s\n"
1177 "  Read Pool:              \"%s\" (From %s)\n"
1178 "  Read Storage:           \"%s\" (From %s)\n"
1179 "  Write Pool:             \"%s\" (From %s)\n"
1180 "  Write Storage:          \"%s\" (From %s)\n"
1181 "  Catalog:                \"%s\" (From %s)\n"
1182 "  Start time:             %s\n"
1183 "  End time:               %s\n"
1184 "  Elapsed time:           %s\n"
1185 "  Priority:               %d\n"
1186 "  SD Files Written:       %s\n"
1187 "  SD Bytes Written:       %s (%sB)\n"
1188 "  Rate:                   %.1f KB/s\n"
1189 "  Volume name(s):         %s\n"
1190 "  Volume Session Id:      %d\n"
1191 "  Volume Session Time:    %d\n"
1192 "  Last Volume Bytes:      %s (%sB)\n"
1193 "  SD Errors:              %d\n"
1194 "  SD termination status:  %s\n"
1195 "  Termination:            %s\n\n"),
1196         my_name, VERSION, LSMDATE, edt,
1197         HOST_OS, DISTNAME, DISTVER,
1198         edit_uint64(jcr->previous_jr.JobId, ec6),
1199         mig_jcr ? edit_uint64(mig_jcr->jr.JobId, ec7) : "0",
1200         edit_uint64(jcr->jr.JobId, ec8),
1201         jcr->jr.Job,
1202         level_to_str(jcr->JobLevel), jcr->since,
1203         jcr->client->name(),
1204         jcr->fileset->name(), jcr->FSCreateTime,
1205         jcr->rpool->name(), jcr->rpool_source,
1206         jcr->rstore?jcr->rstore->name():"*None*", 
1207         NPRT(jcr->rstore_source), 
1208         jcr->pool->name(), jcr->pool_source,
1209         jcr->wstore?jcr->wstore->name():"*None*", 
1210         NPRT(jcr->wstore_source),
1211         jcr->catalog->name(), jcr->catalog_source,
1212         sdt,
1213         edt,
1214         edit_utime(RunTime, elapsed, sizeof(elapsed)),
1215         jcr->JobPriority,
1216         edit_uint64_with_commas(jcr->SDJobFiles, ec1),
1217         edit_uint64_with_commas(jcr->SDJobBytes, ec2),
1218         edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
1219         (float)kbps,
1220         mig_jcr ? mig_jcr->VolumeName : "",
1221         jcr->VolSessionId,
1222         jcr->VolSessionTime,
1223         edit_uint64_with_commas(mr.VolBytes, ec4),
1224         edit_uint64_with_suffix(mr.VolBytes, ec5),
1225         jcr->SDErrors,
1226         sd_term_msg,
1227         term_code);
1228
1229    Dmsg1(100, "migrate_cleanup() mig_jcr=0x%x\n", jcr->mig_jcr);
1230    if (jcr->mig_jcr) {
1231       free_jcr(jcr->mig_jcr);
1232       jcr->mig_jcr = NULL;
1233    }
1234    Dmsg0(100, "Leave migrate_cleanup()\n");
1235 }
1236
1237 /* 
1238  * Return next DBId from comma separated list   
1239  *
1240  * Returns:
1241  *   1 if next DBId returned
1242  *   0 if no more DBIds are in list
1243  *  -1 there is an error
1244  */
1245 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
1246 {
1247    char id[30];
1248    char *q = *p;
1249
1250    id[0] = 0;
1251    for (int i=0; i<(int)sizeof(id); i++) {
1252       if (*q == 0) {
1253          break;
1254       } else if (*q == ',') {
1255          q++;
1256          break;
1257       }
1258       id[i] = *q++;
1259       id[i+1] = 0;
1260    }
1261    if (id[0] == 0) {
1262       return 0;
1263    } else if (!is_a_number(id)) {
1264       return -1;                      /* error */
1265    }
1266    *p = q;
1267    *DBId = str_to_int64(id);
1268    return 1;
1269 }
1270
1271 bool set_migration_wstorage(JCR *jcr, POOL *pool)
1272 {
1273    POOL *wpool = pool->NextPool;
1274
1275    if (!wpool) {
1276       Jmsg(jcr, M_FATAL, 0, _("No Next Pool specification found in Pool \"%s\".\n"),
1277          pool->hdr.name);
1278       return false;
1279    }
1280
1281    if (!wpool->storage || wpool->storage->size() == 0) {
1282       Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Next Pool \"%s\".\n"),
1283          wpool->name());
1284       return false;
1285    }
1286
1287    /* If pool storage specified, use it instead of job storage for backup */
1288    copy_wstorage(jcr, wpool->storage, _("Storage from Pool's NextPool resource"));
1289    return true;
1290 }