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 const int dbglevel = 100;
43 static char OKbootstrap[] = "3000 OK bootstrap\n";
44 static bool get_job_to_migrate(JCR *jcr);
46 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
47 const char *query2, const char *type);
48 static void start_migration_job(JCR *jcr);
51 * Called here before the job is run to do the job
54 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)) {
69 apply_pool_overrides(jcr);
71 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->hdr.name);
72 if (jcr->jr.PoolId == 0) {
76 /* If pool storage specified, use it instead of job storage */
77 copy_storage(jcr, jcr->pool->storage, _("Pool resource"));
80 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Job or Pool.\n"));
84 create_restore_bootstrap_file(jcr);
89 * Do a Migration of a previous job
91 * Returns: false on failure
94 bool do_migration(JCR *jcr)
101 JCR *prev_jcr; /* newly migrated job */
103 if (jcr->previous_jr.JobId == 0 || jcr->ExpectedFiles == 0) {
104 set_jcr_job_status(jcr, JS_Terminated);
105 migration_cleanup(jcr, jcr->JobStatus);
106 return true; /* no work */
109 Dmsg4(dbglevel, "Previous: Name=%s JobId=%d Type=%c Level=%c\n",
110 jcr->previous_jr.Name, jcr->previous_jr.JobId,
111 jcr->previous_jr.JobType, jcr->previous_jr.JobLevel);
113 Dmsg4(dbglevel, "Current: Name=%s JobId=%d Type=%c Level=%c\n",
114 jcr->jr.Name, jcr->jr.JobId,
115 jcr->jr.JobType, jcr->jr.JobLevel);
118 job = (JOB *)GetResWithName(R_JOB, jcr->jr.Name);
119 prev_job = (JOB *)GetResWithName(R_JOB, jcr->previous_jr.Name);
121 if (!job || !prev_job) {
126 * prev_jcr is the new Job that corresponds to the original
127 * job. It "runs" at the same time as the current
128 * migration job and becomes a new backup job that replaces
129 * the original backup job. Most operations on the current
130 * migration jcr are also done on the prev_jcr.
132 prev_jcr = jcr->previous_jcr = new_jcr(sizeof(JCR), dird_free_jcr);
133 memcpy(&prev_jcr->previous_jr, &jcr->previous_jr, sizeof(prev_jcr->previous_jr));
135 /* Turn the prev_jcr into a "real" job */
136 set_jcr_defaults(prev_jcr, prev_job);
137 if (!setup_job(prev_jcr)) {
141 /* Now reset the job record from the previous job */
142 memcpy(&prev_jcr->jr, &jcr->previous_jr, sizeof(prev_jcr->jr));
143 /* Update the jr to reflect the new values of PoolId, FileSetId, and JobId. */
144 prev_jcr->jr.PoolId = jcr->jr.PoolId;
145 prev_jcr->jr.FileSetId = jcr->jr.FileSetId;
146 prev_jcr->jr.JobId = prev_jcr->JobId;
148 Dmsg4(dbglevel, "Prev_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
149 prev_jcr->jr.Name, prev_jcr->jr.JobId,
150 prev_jcr->jr.JobType, prev_jcr->jr.JobLevel);
153 * Get the PoolId used with the original job. Then
154 * find the pool name from the database record.
156 memset(&pr, 0, sizeof(pr));
157 pr.PoolId = prev_jcr->previous_jr.PoolId;
158 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
159 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
160 edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
163 /* Get the pool resource corresponding to the original job */
164 pool = (POOL *)GetResWithName(R_POOL, pr.Name);
166 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
170 /* Check Migration time and High/Low water marks */
173 /* If pool storage specified, use it for restore */
174 copy_storage(prev_jcr, pool->storage, _("Pool resource"));
176 /* If the original backup pool has a NextPool, make sure a
177 * record exists in the database.
179 if (pool->NextPool) {
180 jcr->jr.PoolId = get_or_create_pool_record(jcr, pool->NextPool->hdr.name);
181 if (jcr->jr.PoolId == 0) {
185 * put the "NextPool" resource pointer in our jcr so that we
186 * can pull the Storage reference from it.
188 prev_jcr->pool = jcr->pool = pool->NextPool;
189 prev_jcr->jr.PoolId = jcr->jr.PoolId;
190 pm_strcpy(jcr->pool_source, _("NextPool in Pool resource"));
193 /* If pool storage specified, use it instead of job storage for backup */
194 copy_storage(jcr, jcr->pool->storage, _("Pool resource"));
196 /* Print Job Start message */
197 Jmsg(jcr, M_INFO, 0, _("Start Migration JobId %s, Job=%s\n"),
198 edit_uint64(jcr->JobId, ed1), jcr->Job);
200 set_jcr_job_status(jcr, JS_Running);
201 set_jcr_job_status(prev_jcr, JS_Running);
202 Dmsg2(dbglevel, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
204 /* Update job start record for this migration job */
205 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
206 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
210 Dmsg4(dbglevel, "Prev_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
211 prev_jcr->jr.Name, prev_jcr->jr.JobId,
212 prev_jcr->jr.JobType, prev_jcr->jr.JobLevel);
214 /* Update job start record for migrated job */
215 if (!db_update_job_start_record(prev_jcr, prev_jcr->db, &prev_jcr->jr)) {
216 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(prev_jcr->db));
222 * Open a message channel connection with the Storage
223 * daemon. This is to let him know that our client
224 * will be contacting him for a backup session.
227 Dmsg0(110, "Open connection with storage daemon\n");
228 set_jcr_job_status(jcr, JS_WaitSD);
229 set_jcr_job_status(prev_jcr, JS_WaitSD);
231 * Start conversation with Storage daemon
233 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
236 sd = jcr->store_bsock;
238 * Now start a job with the Storage daemon
240 Dmsg2(dbglevel, "Read store=%s, write store=%s\n",
241 ((STORE *)prev_jcr->storage->first())->hdr.name,
242 ((STORE *)jcr->storage->first())->hdr.name);
243 if (!start_storage_daemon_job(jcr, prev_jcr->storage, jcr->storage)) {
246 Dmsg0(150, "Storage daemon connection OK\n");
248 if (!send_bootstrap_file(jcr, sd) ||
249 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
253 if (!bnet_fsend(sd, "run")) {
258 * Now start a Storage daemon message thread
260 if (!start_storage_daemon_message_thread(jcr)) {
265 set_jcr_job_status(jcr, JS_Running);
266 set_jcr_job_status(prev_jcr, JS_Running);
268 /* Pickup Job termination data */
269 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
270 wait_for_storage_daemon_termination(jcr);
272 set_jcr_job_status(jcr, jcr->SDJobStatus);
273 if (jcr->JobStatus == JS_Terminated) {
274 migration_cleanup(jcr, jcr->JobStatus);
286 * Callback handler make list of DB Ids
288 static int dbid_handler(void *ctx, int num_fields, char **row)
290 idpkt *ids = (idpkt *)ctx;
292 Dmsg3(dbglevel, "count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
293 if (ids->count == 0) {
296 pm_strcat(ids->list, ",");
298 pm_strcat(ids->list, row[0]);
309 static int item_compare(void *item1, void *item2)
311 uitem *i1 = (uitem *)item1;
312 uitem *i2 = (uitem *)item2;
313 return strcmp(i1->item, i2->item);
316 static int unique_name_handler(void *ctx, int num_fields, char **row)
318 dlist *list = (dlist *)ctx;
320 uitem *new_item = (uitem *)malloc(sizeof(uitem));
323 memset(new_item, 0, sizeof(uitem));
324 new_item->item = bstrdup(row[0]);
325 Dmsg1(dbglevel, "Item=%s\n", row[0]);
326 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
327 if (item != new_item) { /* already in list */
328 free(new_item->item);
329 free((char *)new_item);
335 /* Get Job names in Pool */
336 const char *sql_job =
337 "SELECT DISTINCT Job.Name from Job,Pool"
338 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
340 /* Get JobIds from regex'ed Job names */
341 const char *sql_jobids_from_job =
342 "SELECT DISTINCT Job.JobId FROM Job,Pool"
343 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
344 " ORDER by Job.StartTime";
346 /* Get Client names in Pool */
347 const char *sql_client =
348 "SELECT DISTINCT Client.Name from Client,Pool,Job"
349 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
350 " Job.PoolId=Pool.PoolId";
352 /* Get JobIds from regex'ed Client names */
353 const char *sql_jobids_from_client =
354 "SELECT DISTINCT Job.JobId FROM Job,Pool"
355 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
356 " AND Job.ClientId=Client.ClientId "
357 " ORDER by Job.StartTime";
359 /* Get Volume names in Pool */
360 const char *sql_vol =
361 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
362 " VolStatus in ('Full','Used','Error') AND"
363 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
365 /* Get JobIds from regex'ed Volume names */
366 const char *sql_jobids_from_vol =
367 "SELECT DISTINCT Job.JobId FROM Media,JobMedia,Job"
368 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
369 " AND JobMedia.JobId=Job.JobId"
370 " ORDER by Job.StartTime";
376 const char *sql_smallest_vol =
377 "SELECT MediaId FROM Media,Pool WHERE"
378 " VolStatus in ('Full','Used','Error') AND"
379 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
380 " ORDER BY VolBytes ASC LIMIT 1";
382 const char *sql_oldest_vol =
383 "SELECT MediaId FROM Media,Pool WHERE"
384 " VolStatus in ('Full','Used','Error') AND"
385 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
386 " ORDER BY LastWritten ASC LIMIT 1";
388 const char *sql_jobids_from_mediaid =
389 "SELECT DISTINCT Job.JobId FROM JobMedia,Job"
390 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId=%s"
391 " ORDER by Job.StartTime";
393 const char *sql_pool_bytes =
394 "SELECT SUM(VolBytes) FROM Media,Pool WHERE"
395 " VolStatus in ('Full','Used','Error','Append') AND"
396 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
398 const char *sql_vol_bytes =
399 "SELECT MediaId FROM Media,Pool WHERE"
400 " VolStatus in ('Full','Used','Error') AND"
401 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
402 " VolBytes<%s ORDER BY LastWritten ASC LIMIT 1";
405 const char *sql_ujobid =
406 "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
407 " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
408 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
413 * Returns: false on error
414 * true if OK and jcr->previous_jr filled in
416 static bool get_job_to_migrate(JCR *jcr)
419 POOL_MEM query(PM_MESSAGE);
425 ids.list = get_pool_memory(PM_MESSAGE);
426 Dmsg1(dbglevel, "list=%p\n", ids.list);
430 if (jcr->MigrateJobId != 0) {
431 Dmsg1(000, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
432 edit_uint64(jcr->MigrateJobId, ids.list);
435 switch (jcr->job->selection_type) {
437 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
442 if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
447 if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
452 if (!jcr->job->selection_pattern) {
453 Jmsg(jcr, M_FATAL, 0, _("No Migration SQL selection pattern specified.\n"));
456 Dmsg1(dbglevel, "SQL=%s\n", jcr->job->selection_pattern);
457 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
458 dbid_handler, (void *)&ids)) {
459 Jmsg(jcr, M_FATAL, 0,
460 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
466 /***** Below not implemented yet *********/
467 case MT_SMALLEST_VOL:
468 Mmsg(query, sql_smallest_vol, jcr->pool->hdr.name);
469 // Mmsg(query2, sql_jobids_from_mediaid, JobIds);
470 // Dmsg1(000, "Smallest Vol Jobids=%s\n", JobIds);
473 Mmsg(query, sql_oldest_vol, jcr->pool->hdr.name);
474 // Mmsg(query2, sql_jobids_from_mediaid, JobIds);
475 // Dmsg1(000, "Oldest Vol Jobids=%s\n", JobIds);
477 case MT_POOL_OCCUPANCY:
478 Mmsg(query, sql_pool_bytes, jcr->pool->hdr.name);
479 // Dmsg1(000, "Pool Occupancy Jobids=%s\n", JobIds);
482 Dmsg0(000, "Pool time not implemented\n");
485 Jmsg(jcr, M_FATAL, 0, _("Unknown Migration Selection Type.\n"));
491 * Loop over all jobids except the last one, sending
492 * them to start_migration_job(), which will start a job
493 * for each of them. For the last JobId, we handle it below.
496 for (int i=1; i < (int)ids.count; i++) {
498 stat = get_next_jobid_from_list(&p, &JobId);
499 Dmsg2(000, "get_next_jobid stat=%d JobId=%u\n", stat, JobId);
500 jcr->MigrateJobId = JobId;
501 start_migration_job(jcr);
503 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
505 } else if (stat == 0) {
506 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
511 /* Now get the last JobId and handle it in the current job */
513 stat = get_next_jobid_from_list(&p, &JobId);
514 Dmsg2(000, "Last get_next_jobid stat=%d JobId=%u\n", stat, JobId);
516 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
518 } else if (stat == 0) {
519 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
523 jcr->previous_jr.JobId = JobId;
524 Dmsg1(100, "Previous jobid=%d\n", jcr->previous_jr.JobId);
526 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
527 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to migrate. ERR=%s"),
528 edit_int64(jcr->previous_jr.JobId, ed1),
529 db_strerror(jcr->db));
532 Jmsg(jcr, M_INFO, 0, _("Migration using JobId=%d Job=%s\n"),
533 jcr->previous_jr.JobId, jcr->previous_jr.Job);
536 free_pool_memory(ids.list);
540 free_pool_memory(ids.list);
544 static void start_migration_job(JCR *jcr)
546 UAContext *ua = new_ua_context(jcr);
549 Mmsg(ua->cmd, "run %s jobid=%s", jcr->job->hdr.name,
550 edit_uint64(jcr->MigrateJobId, ed1));
551 Dmsg1(dbglevel, "=============== Migration cmd=%s\n", ua->cmd);
552 parse_ua_args(ua); /* parse command */
553 int stat = run_cmd(ua, ua->cmd);
554 // int stat = (int)jcr->MigrateJobId;
556 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
558 Jmsg(jcr, M_INFO, 0, _("Migration JobId %d started.\n"), stat);
564 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
565 const char *query2, const char *type) {
568 uitem *last_item = NULL;
573 POOL_MEM query(PM_MESSAGE);
575 item_chain = New(dlist(item, &item->link));
576 if (!jcr->job->selection_pattern) {
577 Jmsg(jcr, M_FATAL, 0, _("No Migration %s selection pattern specified.\n"),
581 Dmsg1(dbglevel, "regex=%s\n", jcr->job->selection_pattern);
582 /* Compile regex expression */
583 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
585 regerror(rc, &preg, prbuf, sizeof(prbuf));
586 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
587 jcr->job->selection_pattern, prbuf);
590 /* Basic query for names */
591 Mmsg(query, query1, jcr->pool->hdr.name);
592 Dmsg1(dbglevel, "query1=%s\n", query.c_str());
593 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
594 (void *)item_chain)) {
595 Jmsg(jcr, M_FATAL, 0,
596 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
599 /* Now apply the regex to the names and remove any item not matched */
600 foreach_dlist(item, item_chain) {
601 const int nmatch = 30;
602 regmatch_t pmatch[nmatch];
604 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
605 free(last_item->item);
606 item_chain->remove(last_item);
608 Dmsg1(dbglevel, "Item=%s\n", item->item);
609 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
611 last_item = NULL; /* keep this one */
617 free(last_item->item);
618 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
619 item_chain->remove(last_item);
623 * At this point, we have a list of items in item_chain
624 * that have been matched by the regex, so now we need
625 * to look up their jobids.
628 foreach_dlist(item, item_chain) {
629 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
630 Mmsg(query, query2, item->item, jcr->pool->hdr.name);
631 Dmsg1(dbglevel, "query2=%s\n", query.c_str());
632 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
633 Jmsg(jcr, M_FATAL, 0,
634 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
638 if (ids->count == 0) {
639 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
643 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
645 Dmsg0(dbglevel, "After delete item_chain\n");
651 * Release resources allocated during backup.
653 void migration_cleanup(JCR *jcr, int TermCode)
655 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
656 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
657 char ec6[50], ec7[50], ec8[50];
658 char term_code[100], sd_term_msg[100];
659 const char *term_msg;
664 JCR *prev_jcr = jcr->previous_jcr;
665 POOL_MEM query(PM_MESSAGE);
667 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
668 dequeue_messages(jcr); /* display any queued messages */
669 memset(&mr, 0, sizeof(mr));
670 set_jcr_job_status(jcr, TermCode);
671 update_job_end_record(jcr); /* update database */
674 * Check if we actually did something.
675 * prev_jcr is jcr of the newly migrated job.
678 prev_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
679 prev_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
680 prev_jcr->VolSessionId = jcr->VolSessionId;
681 prev_jcr->VolSessionTime = jcr->VolSessionTime;
682 prev_jcr->jr.RealEndTime = 0;
683 prev_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
685 set_jcr_job_status(prev_jcr, TermCode);
688 update_job_end_record(prev_jcr);
690 /* Update final items to set them to the previous job's values */
691 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
692 "JobTDate=%s WHERE JobId=%s",
693 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
694 edit_uint64(jcr->previous_jr.JobTDate, ec1),
695 edit_uint64(prev_jcr->jr.JobId, ec2));
696 db_sql_query(prev_jcr->db, query.c_str(), NULL, NULL);
698 /* Now marke the previous job as migrated */
699 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
700 (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
701 db_sql_query(prev_jcr->db, query.c_str(), NULL, NULL);
703 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
704 Jmsg(jcr, M_WARNING, 0, _("Error getting job record for stats: %s"),
705 db_strerror(jcr->db));
706 set_jcr_job_status(jcr, JS_ErrorTerminated);
709 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
710 if (!db_get_media_record(jcr, jcr->db, &mr)) {
711 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
712 mr.VolumeName, db_strerror(jcr->db));
713 set_jcr_job_status(jcr, JS_ErrorTerminated);
716 update_bootstrap_file(prev_jcr);
718 if (!db_get_job_volume_names(prev_jcr, prev_jcr->db, prev_jcr->jr.JobId, &prev_jcr->VolumeName)) {
720 * Note, if the job has erred, most likely it did not write any
721 * tape, so suppress this "error" message since in that case
722 * it is normal. Or look at it the other way, only for a
723 * normal exit should we complain about this error.
725 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
726 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(prev_jcr->db));
728 prev_jcr->VolumeName[0] = 0; /* none */
732 msg_type = M_INFO; /* by default INFO message */
733 switch (jcr->JobStatus) {
735 if (jcr->Errors || jcr->SDErrors) {
736 term_msg = _("%s OK -- with warnings");
738 term_msg = _("%s OK");
742 case JS_ErrorTerminated:
743 term_msg = _("*** %s Error ***");
744 msg_type = M_ERROR; /* Generate error message */
745 if (jcr->store_bsock) {
746 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
747 if (jcr->SD_msg_chan) {
748 pthread_cancel(jcr->SD_msg_chan);
753 term_msg = _("%s Canceled");
754 if (jcr->store_bsock) {
755 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
756 if (jcr->SD_msg_chan) {
757 pthread_cancel(jcr->SD_msg_chan);
762 term_msg = _("Inappropriate %s term code");
765 bsnprintf(term_code, sizeof(term_code), term_msg, "Migration");
766 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
767 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
768 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
772 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
776 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
778 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
779 " Prev Backup JobId: %s\n"
780 " New Backup JobId: %s\n"
781 " Migration JobId: %s\n"
782 " Migration Job: %s\n"
783 " Backup Level: %s%s\n"
785 " FileSet: \"%s\" %s\n"
786 " Pool: \"%s\" (From %s)\n"
787 " Storage: \"%s\" (From %s)\n"
790 " Elapsed time: %s\n"
792 " SD Files Written: %s\n"
793 " SD Bytes Written: %s (%sB)\n"
795 " Volume name(s): %s\n"
796 " Volume Session Id: %d\n"
797 " Volume Session Time: %d\n"
798 " Last Volume Bytes: %s (%sB)\n"
800 " SD termination status: %s\n"
801 " Termination: %s\n\n"),
805 prev_jcr ? edit_uint64(jcr->previous_jr.JobId, ec6) : "0",
806 prev_jcr ? edit_uint64(prev_jcr->jr.JobId, ec7) : "0",
807 edit_uint64(jcr->jr.JobId, ec8),
809 level_to_str(jcr->JobLevel), jcr->since,
810 jcr->client->hdr.name,
811 jcr->fileset->hdr.name, jcr->FSCreateTime,
812 jcr->pool->hdr.name, jcr->pool_source,
813 jcr->store->hdr.name, jcr->storage_source,
816 edit_utime(RunTime, elapsed, sizeof(elapsed)),
818 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
819 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
820 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
822 prev_jcr ? prev_jcr->VolumeName : "",
825 edit_uint64_with_commas(mr.VolBytes, ec4),
826 edit_uint64_with_suffix(mr.VolBytes, ec5),
831 Dmsg1(100, "migrate_cleanup() previous_jcr=0x%x\n", jcr->previous_jcr);
832 if (jcr->previous_jcr) {
833 free_jcr(jcr->previous_jcr);
834 jcr->previous_jcr = NULL;
836 Dmsg0(100, "Leave migrate_cleanup()\n");