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)) {
277 * Now start a Storage daemon message thread
279 if (!start_storage_daemon_message_thread(jcr)) {
283 if (!bnet_fsend(sd, "run")) {
287 set_jcr_job_status(jcr, JS_Running);
288 set_jcr_job_status(prev_jcr, JS_Running);
290 /* Pickup Job termination data */
291 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
292 wait_for_storage_daemon_termination(jcr);
294 set_jcr_job_status(jcr, jcr->SDJobStatus);
295 if (jcr->JobStatus == JS_Terminated) {
296 migration_cleanup(jcr, jcr->JobStatus);
308 * Callback handler make list of DB Ids
310 static int dbid_handler(void *ctx, int num_fields, char **row)
312 idpkt *ids = (idpkt *)ctx;
314 Dmsg3(000, "count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
315 if (ids->count == 0) {
318 pm_strcat(ids->list, ",");
320 pm_strcat(ids->list, row[0]);
331 static int item_compare(void *item1, void *item2)
333 uitem *i1 = (uitem *)item1;
334 uitem *i2 = (uitem *)item2;
335 return strcmp(i1->item, i2->item);
338 static int unique_name_handler(void *ctx, int num_fields, char **row)
340 dlist *list = (dlist *)ctx;
342 uitem *new_item = (uitem *)malloc(sizeof(uitem));
345 memset(new_item, 0, sizeof(uitem));
346 new_item->item = bstrdup(row[0]);
347 Dmsg1(000, "Item=%s\n", row[0]);
348 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
349 if (item != new_item) { /* already in list */
350 free(new_item->item);
351 free((char *)new_item);
357 /* Get Job names in Pool */
358 const char *sql_job =
359 "SELECT DISTINCT Job.Name from Job,Pool"
360 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
362 /* Get JobIds from regex'ed Job names */
363 const char *sql_jobids_from_job =
364 "SELECT DISTINCT Job.JobId FROM Job,Pool"
365 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
366 " ORDER by Job.StartTime";
368 /* Get Client names in Pool */
369 const char *sql_client =
370 "SELECT DISTINCT Client.Name from Client,Pool,Job"
371 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
372 " Job.PoolId=Pool.PoolId";
374 /* Get JobIds from regex'ed Client names */
375 const char *sql_jobids_from_client =
376 "SELECT DISTINCT Job.JobId FROM Job,Pool"
377 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
378 " AND Job.ClientId=Client.ClientId "
379 " ORDER by Job.StartTime";
381 /* Get Volume names in Pool */
382 const char *sql_vol =
383 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
384 " VolStatus in ('Full','Used','Error') AND"
385 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
387 /* Get JobIds from regex'ed Volume names */
388 const char *sql_jobids_from_vol =
389 "SELECT DISTINCT Job.JobId FROM Media,JobMedia,Job"
390 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
391 " AND JobMedia.JobId=Job.JobId"
392 " ORDER by Job.StartTime";
398 const char *sql_smallest_vol =
399 "SELECT MediaId FROM Media,Pool WHERE"
400 " VolStatus in ('Full','Used','Error') AND"
401 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
402 " ORDER BY VolBytes ASC LIMIT 1";
404 const char *sql_oldest_vol =
405 "SELECT MediaId FROM Media,Pool WHERE"
406 " VolStatus in ('Full','Used','Error') AND"
407 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
408 " ORDER BY LastWritten ASC LIMIT 1";
410 const char *sql_jobids_from_mediaid =
411 "SELECT DISTINCT Job.JobId FROM JobMedia,Job"
412 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId=%s"
413 " ORDER by Job.StartTime";
415 const char *sql_pool_bytes =
416 "SELECT SUM(VolBytes) FROM Media,Pool WHERE"
417 " VolStatus in ('Full','Used','Error','Append') AND"
418 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
420 const char *sql_vol_bytes =
421 "SELECT MediaId FROM Media,Pool WHERE"
422 " VolStatus in ('Full','Used','Error') AND"
423 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
424 " VolBytes<%s ORDER BY LastWritten ASC LIMIT 1";
427 const char *sql_ujobid =
428 "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
429 " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
430 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
435 * Returns: false on error
436 * true if OK and jcr->previous_jr filled in
438 static bool get_job_to_migrate(JCR *jcr)
441 POOL_MEM query(PM_MESSAGE);
447 ids.list = get_pool_memory(PM_MESSAGE);
448 Dmsg1(000, "list=%p\n", ids.list);
452 if (jcr->MigrateJobId != 0) {
453 Dmsg1(000, "previous jobid=%u\n", jcr->MigrateJobId);
454 edit_uint64(jcr->MigrateJobId, ids.list);
457 switch (jcr->job->selection_type) {
459 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
464 if (!regex_find_jobids(jcr, &ids, sql_client,
465 sql_jobids_from_client, "Client")) {
470 if (!regex_find_jobids(jcr, &ids, sql_vol,
471 sql_jobids_from_vol, "Volume")) {
476 if (!jcr->job->selection_pattern) {
477 Jmsg(jcr, M_FATAL, 0, _("No Migration SQL selection pattern specified.\n"));
480 Dmsg1(000, "SQL=%s\n", jcr->job->selection_pattern);
481 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
482 dbid_handler, (void *)&ids)) {
483 Jmsg(jcr, M_FATAL, 0,
484 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
490 /***** Below not implemented yet *********/
491 case MT_SMALLEST_VOL:
492 Mmsg(query, sql_smallest_vol, jcr->pool->hdr.name);
493 // Mmsg(query2, sql_jobids_from_mediaid, JobIds);
494 // Dmsg1(000, "Smallest Vol Jobids=%s\n", JobIds);
497 Mmsg(query, sql_oldest_vol, jcr->pool->hdr.name);
498 // Mmsg(query2, sql_jobids_from_mediaid, JobIds);
499 // Dmsg1(000, "Oldest Vol Jobids=%s\n", JobIds);
501 case MT_POOL_OCCUPANCY:
502 Mmsg(query, sql_pool_bytes, jcr->pool->hdr.name);
503 // Dmsg1(000, "Pool Occupancy Jobids=%s\n", JobIds);
506 Dmsg0(000, "Pool time not implemented\n");
509 Jmsg(jcr, M_FATAL, 0, _("Unknown Migration Selection Type.\n"));
516 stat = get_next_jobid_from_list(&p, &JobId);
517 Dmsg2(000, "get_next_jobid stat=%d JobId=%u\n", stat, JobId);
518 jcr->MigrateJobId = JobId;
519 start_migration_job(jcr);
521 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
523 } else if (stat == 0) {
524 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
528 jcr->previous_jr.JobId = JobId;
529 Dmsg1(000, "Previous jobid=%d\n", jcr->previous_jr.JobId);
531 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
532 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to migrate. ERR=%s"),
533 edit_int64(jcr->previous_jr.JobId, ed1),
534 db_strerror(jcr->db));
537 Jmsg(jcr, M_INFO, 0, _("Migration using JobId=%d Job=%s\n"),
538 jcr->previous_jr.JobId, jcr->previous_jr.Job);
541 free_pool_memory(ids.list);
545 free_pool_memory(ids.list);
549 static void start_migration_job(JCR *jcr)
551 UAContext *ua = new_ua_context(jcr);
554 Mmsg(ua->cmd, "run %s jobid=%s", jcr->job->hdr.name,
555 edit_uint64(jcr->MigrateJobId, ed1));
556 Dmsg1(000, "=============== Migration cmd=%s\n", ua->cmd);
557 parse_ua_args(ua); /* parse command */
558 // int stat = run_cmd(ua, ua->cmd);
559 int stat = (int)jcr->MigrateJobId;
561 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
563 Jmsg(jcr, M_INFO, 0, _("Migration JobId %d started.\n"), stat);
569 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
570 const char *query2, const char *type) {
573 uitem *last_item = NULL;
578 POOL_MEM query(PM_MESSAGE);
580 item_chain = New(dlist(item, &item->link));
581 if (!jcr->job->selection_pattern) {
582 Jmsg(jcr, M_FATAL, 0, _("No Migration %s selection pattern specified.\n"),
586 Dmsg1(000, "regex=%s\n", jcr->job->selection_pattern);
587 /* Compile regex expression */
588 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
590 regerror(rc, &preg, prbuf, sizeof(prbuf));
591 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
592 jcr->job->selection_pattern, prbuf);
595 /* Basic query for names */
596 Mmsg(query, query1, jcr->pool->hdr.name);
597 Dmsg1(000, "query1=%s\n", query.c_str());
598 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
599 (void *)item_chain)) {
600 Jmsg(jcr, M_FATAL, 0,
601 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
604 /* Now apply the regex to the names and remove any item not matched */
605 foreach_dlist(item, item_chain) {
606 const int nmatch = 30;
607 regmatch_t pmatch[nmatch];
609 Dmsg1(000, "Remove item %s\n", last_item->item);
610 free(last_item->item);
611 item_chain->remove(last_item);
613 Dmsg1(000, "Jobitem=%s\n", item->item);
614 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
616 last_item = NULL; /* keep this one */
622 free(last_item->item);
623 Dmsg1(000, "Remove item %s\n", last_item->item);
624 item_chain->remove(last_item);
628 * At this point, we have a list of items in item_chain
629 * that have been matched by the regex, so now we need
630 * to look up their jobids.
633 foreach_dlist(item, item_chain) {
634 Dmsg1(000, "Got Job: %s\n", item->item);
635 Mmsg(query, query2, item->item, jcr->pool->hdr.name);
636 Dmsg1(000, "query2=%s\n", query.c_str());
637 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
638 Jmsg(jcr, M_FATAL, 0,
639 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
643 if (ids->count == 0) {
644 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
648 Dmsg2(000, "Count=%d Jobids=%s\n", ids->count, ids->list);
650 Dmsg0(000, "After delete item_chain\n");
656 * Release resources allocated during backup.
658 void migration_cleanup(JCR *jcr, int TermCode)
660 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
661 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
662 char ec6[50], ec7[50], ec8[50];
663 char term_code[100], sd_term_msg[100];
664 const char *term_msg;
669 JCR *prev_jcr = jcr->previous_jcr;
670 POOL_MEM query(PM_MESSAGE);
672 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
673 dequeue_messages(jcr); /* display any queued messages */
674 memset(&mr, 0, sizeof(mr));
675 set_jcr_job_status(jcr, TermCode);
676 update_job_end_record(jcr); /* update database */
678 /* Check if we actually did something */
680 prev_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
681 prev_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
682 prev_jcr->VolSessionId = jcr->VolSessionId;
683 prev_jcr->VolSessionTime = jcr->VolSessionTime;
685 set_jcr_job_status(prev_jcr, TermCode);
687 update_job_end_record(prev_jcr);
689 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
690 "JobTDate=%s WHERE JobId=%s",
691 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
692 edit_uint64(jcr->previous_jr.JobTDate, ec1),
693 edit_uint64(prev_jcr->jr.JobId, ec2));
694 db_sql_query(prev_jcr->db, query.c_str(), NULL, NULL);
696 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
697 Jmsg(jcr, M_WARNING, 0, _("Error getting job record for stats: %s"),
698 db_strerror(jcr->db));
699 set_jcr_job_status(jcr, JS_ErrorTerminated);
702 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
703 if (!db_get_media_record(jcr, jcr->db, &mr)) {
704 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
705 mr.VolumeName, db_strerror(jcr->db));
706 set_jcr_job_status(jcr, JS_ErrorTerminated);
709 update_bootstrap_file(prev_jcr);
711 if (!db_get_job_volume_names(prev_jcr, prev_jcr->db, prev_jcr->jr.JobId, &prev_jcr->VolumeName)) {
713 * Note, if the job has erred, most likely it did not write any
714 * tape, so suppress this "error" message since in that case
715 * it is normal. Or look at it the other way, only for a
716 * normal exit should we complain about this error.
718 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
719 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(prev_jcr->db));
721 prev_jcr->VolumeName[0] = 0; /* none */
725 msg_type = M_INFO; /* by default INFO message */
726 switch (jcr->JobStatus) {
728 if (jcr->Errors || jcr->SDErrors) {
729 term_msg = _("%s OK -- with warnings");
731 term_msg = _("%s OK");
735 case JS_ErrorTerminated:
736 term_msg = _("*** %s Error ***");
737 msg_type = M_ERROR; /* Generate error message */
738 if (jcr->store_bsock) {
739 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
740 if (jcr->SD_msg_chan) {
741 pthread_cancel(jcr->SD_msg_chan);
746 term_msg = _("%s Canceled");
747 if (jcr->store_bsock) {
748 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
749 if (jcr->SD_msg_chan) {
750 pthread_cancel(jcr->SD_msg_chan);
755 term_msg = _("Inappropriate %s term code");
758 bsnprintf(term_code, sizeof(term_code), term_msg, "Migration");
759 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
760 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
761 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
765 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
769 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
771 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
772 " Prev Backup JobId: %s\n"
773 " New Backup JobId: %s\n"
774 " Migration JobId: %s\n"
775 " Migration Job: %s\n"
776 " Backup Level: %s%s\n"
778 " FileSet: \"%s\" %s\n"
782 " Elapsed time: %s\n"
784 " SD Files Written: %s\n"
785 " SD Bytes Written: %s (%sB)\n"
787 " Volume name(s): %s\n"
788 " Volume Session Id: %d\n"
789 " Volume Session Time: %d\n"
790 " Last Volume Bytes: %s (%sB)\n"
792 " SD termination status: %s\n"
793 " Termination: %s\n\n"),
797 prev_jcr ? edit_uint64(jcr->previous_jr.JobId, ec6) : "0",
798 prev_jcr ? edit_uint64(prev_jcr->jr.JobId, ec7) : "0",
799 edit_uint64(jcr->jr.JobId, ec8),
801 level_to_str(jcr->JobLevel), jcr->since,
802 jcr->client->hdr.name,
803 jcr->fileset->hdr.name, jcr->FSCreateTime,
807 edit_utime(RunTime, elapsed, sizeof(elapsed)),
809 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
810 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
811 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
813 prev_jcr ? prev_jcr->VolumeName : "",
816 edit_uint64_with_commas(mr.VolBytes, ec4),
817 edit_uint64_with_suffix(mr.VolBytes, ec5),
822 Dmsg1(100, "migrate_cleanup() previous_jcr=0x%x\n", jcr->previous_jcr);
823 if (jcr->previous_jcr) {
824 free_jcr(jcr->previous_jcr);
825 jcr->previous_jcr = NULL;
827 Dmsg0(100, "Leave migrate_cleanup()\n");