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