3 * Bacula Director -- migrate.c -- responsible for doing
6 * Kern Sibbald, September MMIV
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
13 * When the Storage daemon finishes the job, update the DB.
18 Copyright (C) 2004-2006 Kern Sibbald
20 This program is free software; you can redistribute it and/or
21 modify it under the terms of the GNU General Public License
22 version 2 as amended with additional clauses defined in the
23 file LICENSE in the main source directory.
25 This program is distributed in the hope that it will be useful,
26 but WITHOUT ANY WARRANTY; without even the implied warranty of
27 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 the file LICENSE for additional details.
36 #include "lib/bregex.h"
41 static char OKbootstrap[] = "3000 OK bootstrap\n";
42 static bool get_job_to_migrate(JCR *jcr);
44 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
45 const char *query2, const char *type);
46 static void start_migration_job(JCR *jcr);
49 * Called here before the job is run to do the job
52 bool do_migration_init(JCR *jcr)
56 /* If we find a job to migrate it is previous_jr.JobId */
57 if (!get_job_to_migrate(jcr)) {
61 if (jcr->previous_jr.JobId == 0) {
62 return true; /* no work */
65 if (!get_or_create_fileset_record(jcr)) {
70 * Get the Pool record -- first apply any level defined pools
72 switch (jcr->previous_jr.JobLevel) {
75 jcr->pool = jcr->full_pool;
80 jcr->pool = jcr->inc_pool;
85 jcr->pool = jcr->dif_pool;
89 memset(&pr, 0, sizeof(pr));
90 bstrncpy(pr.Name, jcr->pool->hdr.name, sizeof(pr.Name));
92 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
93 /* Try to create the pool */
94 if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
95 Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
96 db_strerror(jcr->db));
99 Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
102 jcr->jr.PoolId = pr.PoolId;
104 /* If pool storage specified, use it instead of job storage */
105 copy_storage(jcr, jcr->pool->storage);
108 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Job or Pool.\n"));
112 if (!create_restore_bootstrap_file(jcr)) {
119 * Do a Migration of a previous job
121 * Returns: false on failure
124 bool do_migration(JCR *jcr)
133 if (jcr->previous_jr.JobId == 0) {
134 set_jcr_job_status(jcr, JS_Terminated);
135 migration_cleanup(jcr, jcr->JobStatus);
136 return true; /* no work */
139 Dmsg4(000, "Previous:: Name=%s JobId=%d Type=%c Level=%c\n",
140 jcr->previous_jr.Name, jcr->previous_jr.JobId,
141 jcr->previous_jr.JobType, jcr->previous_jr.JobLevel);
143 Dmsg4(000, "Current: Name=%s JobId=%d Type=%c Level=%c\n",
144 jcr->jr.Name, jcr->jr.JobId,
145 jcr->jr.JobType, jcr->jr.JobLevel);
148 job = (JOB *)GetResWithName(R_JOB, jcr->jr.Name);
149 prev_job = (JOB *)GetResWithName(R_JOB, jcr->previous_jr.Name);
151 if (!job || !prev_job) {
156 * prev_jcr is the new Job that corresponds to the original
157 * job. It "runs" at the same time as the current
158 * migration job and becomes a new backup job that replaces
159 * the original backup job. Most operations on the current
160 * migration jcr are also done on the prev_jcr.
162 prev_jcr = jcr->previous_jcr = new_jcr(sizeof(JCR), dird_free_jcr);
163 memcpy(&prev_jcr->previous_jr, &jcr->previous_jr, sizeof(prev_jcr->previous_jr));
165 /* Turn the prev_jcr into a "real" job */
166 set_jcr_defaults(prev_jcr, prev_job);
167 if (!setup_job(prev_jcr)) {
170 /* Set output PoolId and FileSetId. */
171 prev_jcr->jr.PoolId = jcr->jr.PoolId;
172 prev_jcr->jr.FileSetId = jcr->jr.FileSetId;
175 * Get the PoolId used with the original job. Then
176 * find the pool name from the database record.
178 memset(&pr, 0, sizeof(pr));
179 pr.PoolId = prev_jcr->previous_jr.PoolId;
180 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
181 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
182 edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
185 /* Get the pool resource corresponding to the original job */
186 pool = (POOL *)GetResWithName(R_POOL, pr.Name);
188 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
192 /* Check Migration time and High/Low water marks */
195 /* If pool storage specified, use it for restore */
196 copy_storage(prev_jcr, pool->storage);
198 /* If the original backup pool has a NextPool, make sure a
199 * record exists in the database.
201 if (pool->NextPool) {
202 memset(&pr, 0, sizeof(pr));
203 bstrncpy(pr.Name, pool->NextPool->hdr.name, sizeof(pr.Name));
205 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
206 /* Try to create the pool */
207 if (create_pool(jcr, jcr->db, pool->NextPool, POOL_OP_CREATE) < 0) {
208 Jmsg(jcr, M_FATAL, 0, _("Pool \"%s\" not in database. %s"), pr.Name,
209 db_strerror(jcr->db));
212 Jmsg(jcr, M_INFO, 0, _("Pool \"%s\" created in database.\n"), pr.Name);
216 * put the "NextPool" resource pointer in our jcr so that we
217 * can pull the Storage reference from it.
219 prev_jcr->pool = jcr->pool = pool->NextPool;
220 prev_jcr->jr.PoolId = jcr->jr.PoolId = pr.PoolId;
223 /* If pool storage specified, use it instead of job storage for backup */
224 copy_storage(jcr, jcr->pool->storage);
226 /* Print Job Start message */
227 Jmsg(jcr, M_INFO, 0, _("Start Migration JobId %s, Job=%s\n"),
228 edit_uint64(jcr->JobId, ed1), jcr->Job);
230 set_jcr_job_status(jcr, JS_Running);
231 set_jcr_job_status(prev_jcr, JS_Running);
232 Dmsg2(000, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
233 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
234 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
238 if (!db_update_job_start_record(prev_jcr, prev_jcr->db, &prev_jcr->jr)) {
239 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(prev_jcr->db));
245 * Open a message channel connection with the Storage
246 * daemon. This is to let him know that our client
247 * will be contacting him for a backup session.
250 Dmsg0(110, "Open connection with storage daemon\n");
251 set_jcr_job_status(jcr, JS_WaitSD);
252 set_jcr_job_status(prev_jcr, JS_WaitSD);
254 * Start conversation with Storage daemon
256 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
259 sd = jcr->store_bsock;
261 * Now start a job with the Storage daemon
263 Dmsg2(000, "Read store=%s, write store=%s\n",
264 ((STORE *)prev_jcr->storage->first())->hdr.name,
265 ((STORE *)jcr->storage->first())->hdr.name);
266 if (!start_storage_daemon_job(jcr, prev_jcr->storage, jcr->storage)) {
269 Dmsg0(150, "Storage daemon connection OK\n");
271 if (!send_bootstrap_file(jcr, sd) ||
272 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
276 if (!bnet_fsend(sd, "run")) {
281 * Now start a Storage daemon message thread
283 if (!start_storage_daemon_message_thread(jcr)) {
288 set_jcr_job_status(jcr, JS_Running);
289 set_jcr_job_status(prev_jcr, JS_Running);
291 /* Pickup Job termination data */
292 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
293 wait_for_storage_daemon_termination(jcr);
295 set_jcr_job_status(jcr, jcr->SDJobStatus);
296 if (jcr->JobStatus == JS_Terminated) {
297 migration_cleanup(jcr, jcr->JobStatus);
309 * Callback handler make list of DB Ids
311 static int dbid_handler(void *ctx, int num_fields, char **row)
313 idpkt *ids = (idpkt *)ctx;
315 Dmsg3(000, "count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
316 if (ids->count == 0) {
319 pm_strcat(ids->list, ",");
321 pm_strcat(ids->list, row[0]);
332 static int item_compare(void *item1, void *item2)
334 uitem *i1 = (uitem *)item1;
335 uitem *i2 = (uitem *)item2;
336 return strcmp(i1->item, i2->item);
339 static int unique_name_handler(void *ctx, int num_fields, char **row)
341 dlist *list = (dlist *)ctx;
343 uitem *new_item = (uitem *)malloc(sizeof(uitem));
346 memset(new_item, 0, sizeof(uitem));
347 new_item->item = bstrdup(row[0]);
348 Dmsg1(000, "Item=%s\n", row[0]);
349 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
350 if (item != new_item) { /* already in list */
351 free(new_item->item);
352 free((char *)new_item);
358 /* Get Job names in Pool */
359 const char *sql_job =
360 "SELECT DISTINCT Job.Name from Job,Pool"
361 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
363 /* Get JobIds from regex'ed Job names */
364 const char *sql_jobids_from_job =
365 "SELECT DISTINCT Job.JobId FROM Job,Pool"
366 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
367 " ORDER by Job.StartTime";
369 /* Get Client names in Pool */
370 const char *sql_client =
371 "SELECT DISTINCT Client.Name from Client,Pool,Job"
372 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
373 " Job.PoolId=Pool.PoolId";
375 /* Get JobIds from regex'ed Client names */
376 const char *sql_jobids_from_client =
377 "SELECT DISTINCT Job.JobId FROM Job,Pool"
378 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
379 " AND Job.ClientId=Client.ClientId "
380 " ORDER by Job.StartTime";
382 /* Get Volume names in Pool */
383 const char *sql_vol =
384 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
385 " VolStatus in ('Full','Used','Error') AND"
386 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
388 /* Get JobIds from regex'ed Volume names */
389 const char *sql_jobids_from_vol =
390 "SELECT DISTINCT Job.JobId FROM Media,JobMedia,Job"
391 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
392 " AND JobMedia.JobId=Job.JobId"
393 " ORDER by Job.StartTime";
399 const char *sql_smallest_vol =
400 "SELECT MediaId FROM Media,Pool WHERE"
401 " VolStatus in ('Full','Used','Error') AND"
402 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
403 " ORDER BY VolBytes ASC LIMIT 1";
405 const char *sql_oldest_vol =
406 "SELECT MediaId FROM Media,Pool WHERE"
407 " VolStatus in ('Full','Used','Error') AND"
408 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
409 " ORDER BY LastWritten ASC LIMIT 1";
411 const char *sql_jobids_from_mediaid =
412 "SELECT DISTINCT Job.JobId FROM JobMedia,Job"
413 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId=%s"
414 " ORDER by Job.StartTime";
416 const char *sql_pool_bytes =
417 "SELECT SUM(VolBytes) FROM Media,Pool WHERE"
418 " VolStatus in ('Full','Used','Error','Append') AND"
419 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
421 const char *sql_vol_bytes =
422 "SELECT MediaId FROM Media,Pool WHERE"
423 " VolStatus in ('Full','Used','Error') AND"
424 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
425 " VolBytes<%s ORDER BY LastWritten ASC LIMIT 1";
428 const char *sql_ujobid =
429 "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
430 " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
431 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
436 * Returns: false on error
437 * true if OK and jcr->previous_jr filled in
439 static bool get_job_to_migrate(JCR *jcr)
442 POOL_MEM query(PM_MESSAGE);
448 ids.list = get_pool_memory(PM_MESSAGE);
449 Dmsg1(000, "list=%p\n", ids.list);
453 if (jcr->MigrateJobId != 0) {
454 Dmsg1(000, "previous jobid=%u\n", jcr->MigrateJobId);
455 edit_uint64(jcr->MigrateJobId, ids.list);
458 switch (jcr->job->selection_type) {
460 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
465 if (!regex_find_jobids(jcr, &ids, sql_client,
466 sql_jobids_from_client, "Client")) {
471 if (!regex_find_jobids(jcr, &ids, sql_vol,
472 sql_jobids_from_vol, "Volume")) {
477 if (!jcr->job->selection_pattern) {
478 Jmsg(jcr, M_FATAL, 0, _("No Migration SQL selection pattern specified.\n"));
481 Dmsg1(000, "SQL=%s\n", jcr->job->selection_pattern);
482 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
483 dbid_handler, (void *)&ids)) {
484 Jmsg(jcr, M_FATAL, 0,
485 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
491 /***** Below not implemented yet *********/
492 case MT_SMALLEST_VOL:
493 Mmsg(query, sql_smallest_vol, jcr->pool->hdr.name);
494 // Mmsg(query2, sql_jobids_from_mediaid, JobIds);
495 // Dmsg1(000, "Smallest Vol Jobids=%s\n", JobIds);
498 Mmsg(query, sql_oldest_vol, jcr->pool->hdr.name);
499 // Mmsg(query2, sql_jobids_from_mediaid, JobIds);
500 // Dmsg1(000, "Oldest Vol Jobids=%s\n", JobIds);
502 case MT_POOL_OCCUPANCY:
503 Mmsg(query, sql_pool_bytes, jcr->pool->hdr.name);
504 // Dmsg1(000, "Pool Occupancy Jobids=%s\n", JobIds);
507 Dmsg0(000, "Pool time not implemented\n");
510 Jmsg(jcr, M_FATAL, 0, _("Unknown Migration Selection Type.\n"));
517 stat = get_next_jobid_from_list(&p, &JobId);
518 Dmsg2(000, "get_next_jobid stat=%d JobId=%u\n", stat, JobId);
519 jcr->MigrateJobId = JobId;
520 start_migration_job(jcr);
522 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
524 } else if (stat == 0) {
525 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
529 jcr->previous_jr.JobId = JobId;
530 Dmsg1(000, "Previous jobid=%d\n", jcr->previous_jr.JobId);
532 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
533 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to migrate. ERR=%s"),
534 edit_int64(jcr->previous_jr.JobId, ed1),
535 db_strerror(jcr->db));
538 Jmsg(jcr, M_INFO, 0, _("Migration using JobId=%d Job=%s\n"),
539 jcr->previous_jr.JobId, jcr->previous_jr.Job);
542 free_pool_memory(ids.list);
546 free_pool_memory(ids.list);
550 static void start_migration_job(JCR *jcr)
552 UAContext *ua = new_ua_context(jcr);
555 Mmsg(ua->cmd, "run %s jobid=%s", jcr->job->hdr.name,
556 edit_uint64(jcr->MigrateJobId, ed1));
557 Dmsg1(000, "=============== Migration cmd=%s\n", ua->cmd);
558 parse_ua_args(ua); /* parse command */
559 // int stat = run_cmd(ua, ua->cmd);
560 int stat = (int)jcr->MigrateJobId;
562 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
564 Jmsg(jcr, M_INFO, 0, _("Migration JobId %d started.\n"), stat);
570 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
571 const char *query2, const char *type) {
574 uitem *last_item = NULL;
579 POOL_MEM query(PM_MESSAGE);
581 item_chain = New(dlist(item, &item->link));
582 if (!jcr->job->selection_pattern) {
583 Jmsg(jcr, M_FATAL, 0, _("No Migration %s selection pattern specified.\n"),
587 Dmsg1(000, "regex=%s\n", jcr->job->selection_pattern);
588 /* Compile regex expression */
589 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
591 regerror(rc, &preg, prbuf, sizeof(prbuf));
592 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
593 jcr->job->selection_pattern, prbuf);
596 /* Basic query for names */
597 Mmsg(query, query1, jcr->pool->hdr.name);
598 Dmsg1(000, "query1=%s\n", query.c_str());
599 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
600 (void *)item_chain)) {
601 Jmsg(jcr, M_FATAL, 0,
602 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
605 /* Now apply the regex to the names and remove any item not matched */
606 foreach_dlist(item, item_chain) {
607 const int nmatch = 30;
608 regmatch_t pmatch[nmatch];
610 Dmsg1(000, "Remove item %s\n", last_item->item);
611 free(last_item->item);
612 item_chain->remove(last_item);
614 Dmsg1(000, "Jobitem=%s\n", item->item);
615 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
617 last_item = NULL; /* keep this one */
623 free(last_item->item);
624 Dmsg1(000, "Remove item %s\n", last_item->item);
625 item_chain->remove(last_item);
629 * At this point, we have a list of items in item_chain
630 * that have been matched by the regex, so now we need
631 * to look up their jobids.
634 foreach_dlist(item, item_chain) {
635 Dmsg1(000, "Got Job: %s\n", item->item);
636 Mmsg(query, query2, item->item, jcr->pool->hdr.name);
637 Dmsg1(000, "query2=%s\n", query.c_str());
638 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
639 Jmsg(jcr, M_FATAL, 0,
640 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
644 if (ids->count == 0) {
645 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
649 Dmsg2(000, "Count=%d Jobids=%s\n", ids->count, ids->list);
651 Dmsg0(000, "After delete item_chain\n");
657 * Release resources allocated during backup.
659 void migration_cleanup(JCR *jcr, int TermCode)
661 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
662 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
663 char ec6[50], ec7[50], ec8[50];
664 char term_code[100], sd_term_msg[100];
665 const char *term_msg;
670 JCR *prev_jcr = jcr->previous_jcr;
671 POOL_MEM query(PM_MESSAGE);
673 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
674 dequeue_messages(jcr); /* display any queued messages */
675 memset(&mr, 0, sizeof(mr));
676 set_jcr_job_status(jcr, TermCode);
677 update_job_end_record(jcr); /* update database */
679 /* Check if we actually did something */
681 prev_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
682 prev_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
683 prev_jcr->VolSessionId = jcr->VolSessionId;
684 prev_jcr->VolSessionTime = jcr->VolSessionTime;
686 set_jcr_job_status(prev_jcr, TermCode);
688 update_job_end_record(prev_jcr);
690 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
691 "JobTDate=%s WHERE JobId=%s",
692 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
693 edit_uint64(jcr->previous_jr.JobTDate, ec1),
694 edit_uint64(prev_jcr->jr.JobId, ec2));
695 db_sql_query(prev_jcr->db, query.c_str(), NULL, NULL);
697 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
698 Jmsg(jcr, M_WARNING, 0, _("Error getting job record for stats: %s"),
699 db_strerror(jcr->db));
700 set_jcr_job_status(jcr, JS_ErrorTerminated);
703 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
704 if (!db_get_media_record(jcr, jcr->db, &mr)) {
705 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
706 mr.VolumeName, db_strerror(jcr->db));
707 set_jcr_job_status(jcr, JS_ErrorTerminated);
710 update_bootstrap_file(prev_jcr);
712 if (!db_get_job_volume_names(prev_jcr, prev_jcr->db, prev_jcr->jr.JobId, &prev_jcr->VolumeName)) {
714 * Note, if the job has erred, most likely it did not write any
715 * tape, so suppress this "error" message since in that case
716 * it is normal. Or look at it the other way, only for a
717 * normal exit should we complain about this error.
719 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
720 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(prev_jcr->db));
722 prev_jcr->VolumeName[0] = 0; /* none */
726 msg_type = M_INFO; /* by default INFO message */
727 switch (jcr->JobStatus) {
729 if (jcr->Errors || jcr->SDErrors) {
730 term_msg = _("%s OK -- with warnings");
732 term_msg = _("%s OK");
736 case JS_ErrorTerminated:
737 term_msg = _("*** %s Error ***");
738 msg_type = M_ERROR; /* Generate error message */
739 if (jcr->store_bsock) {
740 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
741 if (jcr->SD_msg_chan) {
742 pthread_cancel(jcr->SD_msg_chan);
747 term_msg = _("%s Canceled");
748 if (jcr->store_bsock) {
749 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
750 if (jcr->SD_msg_chan) {
751 pthread_cancel(jcr->SD_msg_chan);
756 term_msg = _("Inappropriate %s term code");
759 bsnprintf(term_code, sizeof(term_code), term_msg, "Migration");
760 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
761 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
762 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
766 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
770 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
772 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
773 " Prev Backup JobId: %s\n"
774 " New Backup JobId: %s\n"
775 " Migration JobId: %s\n"
776 " Migration Job: %s\n"
777 " Backup Level: %s%s\n"
779 " FileSet: \"%s\" %s\n"
783 " Elapsed time: %s\n"
785 " SD Files Written: %s\n"
786 " SD Bytes Written: %s (%sB)\n"
788 " Volume name(s): %s\n"
789 " Volume Session Id: %d\n"
790 " Volume Session Time: %d\n"
791 " Last Volume Bytes: %s (%sB)\n"
793 " SD termination status: %s\n"
794 " Termination: %s\n\n"),
798 prev_jcr ? edit_uint64(jcr->previous_jr.JobId, ec6) : "0",
799 prev_jcr ? edit_uint64(prev_jcr->jr.JobId, ec7) : "0",
800 edit_uint64(jcr->jr.JobId, ec8),
802 level_to_str(jcr->JobLevel), jcr->since,
803 jcr->client->hdr.name,
804 jcr->fileset->hdr.name, jcr->FSCreateTime,
808 edit_utime(RunTime, elapsed, sizeof(elapsed)),
810 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
811 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
812 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
814 prev_jcr ? prev_jcr->VolumeName : "",
817 edit_uint64_with_commas(mr.VolBytes, ec4),
818 edit_uint64_with_suffix(mr.VolBytes, ec5),
823 Dmsg1(100, "migrate_cleanup() previous_jcr=0x%x\n", jcr->previous_jcr);
824 if (jcr->previous_jcr) {
825 free_jcr(jcr->previous_jcr);
826 jcr->previous_jcr = NULL;
828 Dmsg0(100, "Leave migrate_cleanup()\n");