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)
54 /* If we find a job to migrate it is previous_jr.JobId */
55 if (!get_job_to_migrate(jcr)) {
59 if (jcr->previous_jr.JobId == 0) {
60 return true; /* no work */
63 if (!get_or_create_fileset_record(jcr)) {
67 apply_pool_overrides(jcr);
69 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->hdr.name);
70 if (jcr->jr.PoolId == 0) {
74 /* If pool storage specified, use it instead of job storage */
75 copy_storage(jcr, jcr->pool->storage, _("Pool resource"));
78 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Job or Pool.\n"));
82 if (!create_restore_bootstrap_file(jcr)) {
89 * Do a Migration of a previous job
91 * Returns: false on failure
94 bool do_migration(JCR *jcr)
103 if (jcr->previous_jr.JobId == 0) {
104 set_jcr_job_status(jcr, JS_Terminated);
105 migration_cleanup(jcr, jcr->JobStatus);
106 return true; /* no work */
109 Dmsg4(000, "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(000, "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)) {
140 /* Set output PoolId and FileSetId. */
141 prev_jcr->jr.PoolId = jcr->jr.PoolId;
142 prev_jcr->jr.FileSetId = jcr->jr.FileSetId;
145 * Get the PoolId used with the original job. Then
146 * find the pool name from the database record.
148 memset(&pr, 0, sizeof(pr));
149 pr.PoolId = prev_jcr->previous_jr.PoolId;
150 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
151 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
152 edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
155 /* Get the pool resource corresponding to the original job */
156 pool = (POOL *)GetResWithName(R_POOL, pr.Name);
158 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
162 /* Check Migration time and High/Low water marks */
165 /* If pool storage specified, use it for restore */
166 copy_storage(prev_jcr, pool->storage, _("Pool resource"));
168 /* If the original backup pool has a NextPool, make sure a
169 * record exists in the database.
171 if (pool->NextPool) {
172 jcr->jr.PoolId = get_or_create_pool_record(jcr, pool->NextPool->hdr.name);
173 if (jcr->jr.PoolId == 0) {
177 * put the "NextPool" resource pointer in our jcr so that we
178 * can pull the Storage reference from it.
180 prev_jcr->pool = jcr->pool = pool->NextPool;
181 prev_jcr->jr.PoolId = jcr->jr.PoolId;
182 pm_strcpy(jcr->pool_source, _("NextPool in Pool resource"));
185 /* If pool storage specified, use it instead of job storage for backup */
186 copy_storage(jcr, jcr->pool->storage, _("Pool resource"));
188 /* Print Job Start message */
189 Jmsg(jcr, M_INFO, 0, _("Start Migration JobId %s, Job=%s\n"),
190 edit_uint64(jcr->JobId, ed1), jcr->Job);
192 set_jcr_job_status(jcr, JS_Running);
193 set_jcr_job_status(prev_jcr, JS_Running);
194 Dmsg2(000, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
195 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
196 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
200 if (!db_update_job_start_record(prev_jcr, prev_jcr->db, &prev_jcr->jr)) {
201 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(prev_jcr->db));
207 * Open a message channel connection with the Storage
208 * daemon. This is to let him know that our client
209 * will be contacting him for a backup session.
212 Dmsg0(110, "Open connection with storage daemon\n");
213 set_jcr_job_status(jcr, JS_WaitSD);
214 set_jcr_job_status(prev_jcr, JS_WaitSD);
216 * Start conversation with Storage daemon
218 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
221 sd = jcr->store_bsock;
223 * Now start a job with the Storage daemon
225 Dmsg2(000, "Read store=%s, write store=%s\n",
226 ((STORE *)prev_jcr->storage->first())->hdr.name,
227 ((STORE *)jcr->storage->first())->hdr.name);
228 if (!start_storage_daemon_job(jcr, prev_jcr->storage, jcr->storage)) {
231 Dmsg0(150, "Storage daemon connection OK\n");
233 if (!send_bootstrap_file(jcr, sd) ||
234 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
238 if (!bnet_fsend(sd, "run")) {
243 * Now start a Storage daemon message thread
245 if (!start_storage_daemon_message_thread(jcr)) {
250 set_jcr_job_status(jcr, JS_Running);
251 set_jcr_job_status(prev_jcr, JS_Running);
253 /* Pickup Job termination data */
254 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
255 wait_for_storage_daemon_termination(jcr);
257 set_jcr_job_status(jcr, jcr->SDJobStatus);
258 if (jcr->JobStatus == JS_Terminated) {
259 migration_cleanup(jcr, jcr->JobStatus);
271 * Callback handler make list of DB Ids
273 static int dbid_handler(void *ctx, int num_fields, char **row)
275 idpkt *ids = (idpkt *)ctx;
277 Dmsg3(000, "count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
278 if (ids->count == 0) {
281 pm_strcat(ids->list, ",");
283 pm_strcat(ids->list, row[0]);
294 static int item_compare(void *item1, void *item2)
296 uitem *i1 = (uitem *)item1;
297 uitem *i2 = (uitem *)item2;
298 return strcmp(i1->item, i2->item);
301 static int unique_name_handler(void *ctx, int num_fields, char **row)
303 dlist *list = (dlist *)ctx;
305 uitem *new_item = (uitem *)malloc(sizeof(uitem));
308 memset(new_item, 0, sizeof(uitem));
309 new_item->item = bstrdup(row[0]);
310 Dmsg1(000, "Item=%s\n", row[0]);
311 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
312 if (item != new_item) { /* already in list */
313 free(new_item->item);
314 free((char *)new_item);
320 /* Get Job names in Pool */
321 const char *sql_job =
322 "SELECT DISTINCT Job.Name from Job,Pool"
323 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
325 /* Get JobIds from regex'ed Job names */
326 const char *sql_jobids_from_job =
327 "SELECT DISTINCT Job.JobId FROM Job,Pool"
328 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
329 " ORDER by Job.StartTime";
331 /* Get Client names in Pool */
332 const char *sql_client =
333 "SELECT DISTINCT Client.Name from Client,Pool,Job"
334 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
335 " Job.PoolId=Pool.PoolId";
337 /* Get JobIds from regex'ed Client names */
338 const char *sql_jobids_from_client =
339 "SELECT DISTINCT Job.JobId FROM Job,Pool"
340 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
341 " AND Job.ClientId=Client.ClientId "
342 " ORDER by Job.StartTime";
344 /* Get Volume names in Pool */
345 const char *sql_vol =
346 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
347 " VolStatus in ('Full','Used','Error') AND"
348 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
350 /* Get JobIds from regex'ed Volume names */
351 const char *sql_jobids_from_vol =
352 "SELECT DISTINCT Job.JobId FROM Media,JobMedia,Job"
353 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
354 " AND JobMedia.JobId=Job.JobId"
355 " ORDER by Job.StartTime";
361 const char *sql_smallest_vol =
362 "SELECT MediaId FROM Media,Pool WHERE"
363 " VolStatus in ('Full','Used','Error') AND"
364 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
365 " ORDER BY VolBytes ASC LIMIT 1";
367 const char *sql_oldest_vol =
368 "SELECT MediaId FROM Media,Pool WHERE"
369 " VolStatus in ('Full','Used','Error') AND"
370 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
371 " ORDER BY LastWritten ASC LIMIT 1";
373 const char *sql_jobids_from_mediaid =
374 "SELECT DISTINCT Job.JobId FROM JobMedia,Job"
375 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId=%s"
376 " ORDER by Job.StartTime";
378 const char *sql_pool_bytes =
379 "SELECT SUM(VolBytes) FROM Media,Pool WHERE"
380 " VolStatus in ('Full','Used','Error','Append') AND"
381 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
383 const char *sql_vol_bytes =
384 "SELECT MediaId FROM Media,Pool WHERE"
385 " VolStatus in ('Full','Used','Error') AND"
386 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
387 " VolBytes<%s ORDER BY LastWritten ASC LIMIT 1";
390 const char *sql_ujobid =
391 "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
392 " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
393 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
398 * Returns: false on error
399 * true if OK and jcr->previous_jr filled in
401 static bool get_job_to_migrate(JCR *jcr)
404 POOL_MEM query(PM_MESSAGE);
410 ids.list = get_pool_memory(PM_MESSAGE);
411 Dmsg1(000, "list=%p\n", ids.list);
415 if (jcr->MigrateJobId != 0) {
416 Dmsg1(000, "previous jobid=%u\n", jcr->MigrateJobId);
417 edit_uint64(jcr->MigrateJobId, ids.list);
420 switch (jcr->job->selection_type) {
422 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
427 if (!regex_find_jobids(jcr, &ids, sql_client,
428 sql_jobids_from_client, "Client")) {
433 if (!regex_find_jobids(jcr, &ids, sql_vol,
434 sql_jobids_from_vol, "Volume")) {
439 if (!jcr->job->selection_pattern) {
440 Jmsg(jcr, M_FATAL, 0, _("No Migration SQL selection pattern specified.\n"));
443 Dmsg1(000, "SQL=%s\n", jcr->job->selection_pattern);
444 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
445 dbid_handler, (void *)&ids)) {
446 Jmsg(jcr, M_FATAL, 0,
447 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
453 /***** Below not implemented yet *********/
454 case MT_SMALLEST_VOL:
455 Mmsg(query, sql_smallest_vol, jcr->pool->hdr.name);
456 // Mmsg(query2, sql_jobids_from_mediaid, JobIds);
457 // Dmsg1(000, "Smallest Vol Jobids=%s\n", JobIds);
460 Mmsg(query, sql_oldest_vol, jcr->pool->hdr.name);
461 // Mmsg(query2, sql_jobids_from_mediaid, JobIds);
462 // Dmsg1(000, "Oldest Vol Jobids=%s\n", JobIds);
464 case MT_POOL_OCCUPANCY:
465 Mmsg(query, sql_pool_bytes, jcr->pool->hdr.name);
466 // Dmsg1(000, "Pool Occupancy Jobids=%s\n", JobIds);
469 Dmsg0(000, "Pool time not implemented\n");
472 Jmsg(jcr, M_FATAL, 0, _("Unknown Migration Selection Type.\n"));
479 stat = get_next_jobid_from_list(&p, &JobId);
480 Dmsg2(000, "get_next_jobid stat=%d JobId=%u\n", stat, JobId);
481 jcr->MigrateJobId = JobId;
482 start_migration_job(jcr);
484 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
486 } else if (stat == 0) {
487 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
491 jcr->previous_jr.JobId = JobId;
492 Dmsg1(000, "Previous jobid=%d\n", jcr->previous_jr.JobId);
494 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
495 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to migrate. ERR=%s"),
496 edit_int64(jcr->previous_jr.JobId, ed1),
497 db_strerror(jcr->db));
500 Jmsg(jcr, M_INFO, 0, _("Migration using JobId=%d Job=%s\n"),
501 jcr->previous_jr.JobId, jcr->previous_jr.Job);
504 free_pool_memory(ids.list);
508 free_pool_memory(ids.list);
512 static void start_migration_job(JCR *jcr)
514 UAContext *ua = new_ua_context(jcr);
517 Mmsg(ua->cmd, "run %s jobid=%s", jcr->job->hdr.name,
518 edit_uint64(jcr->MigrateJobId, ed1));
519 Dmsg1(000, "=============== Migration cmd=%s\n", ua->cmd);
520 parse_ua_args(ua); /* parse command */
521 // int stat = run_cmd(ua, ua->cmd);
522 int stat = (int)jcr->MigrateJobId;
524 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
526 Jmsg(jcr, M_INFO, 0, _("Migration JobId %d started.\n"), stat);
532 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
533 const char *query2, const char *type) {
536 uitem *last_item = NULL;
541 POOL_MEM query(PM_MESSAGE);
543 item_chain = New(dlist(item, &item->link));
544 if (!jcr->job->selection_pattern) {
545 Jmsg(jcr, M_FATAL, 0, _("No Migration %s selection pattern specified.\n"),
549 Dmsg1(000, "regex=%s\n", jcr->job->selection_pattern);
550 /* Compile regex expression */
551 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
553 regerror(rc, &preg, prbuf, sizeof(prbuf));
554 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
555 jcr->job->selection_pattern, prbuf);
558 /* Basic query for names */
559 Mmsg(query, query1, jcr->pool->hdr.name);
560 Dmsg1(000, "query1=%s\n", query.c_str());
561 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
562 (void *)item_chain)) {
563 Jmsg(jcr, M_FATAL, 0,
564 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
567 /* Now apply the regex to the names and remove any item not matched */
568 foreach_dlist(item, item_chain) {
569 const int nmatch = 30;
570 regmatch_t pmatch[nmatch];
572 Dmsg1(000, "Remove item %s\n", last_item->item);
573 free(last_item->item);
574 item_chain->remove(last_item);
576 Dmsg1(000, "Jobitem=%s\n", item->item);
577 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
579 last_item = NULL; /* keep this one */
585 free(last_item->item);
586 Dmsg1(000, "Remove item %s\n", last_item->item);
587 item_chain->remove(last_item);
591 * At this point, we have a list of items in item_chain
592 * that have been matched by the regex, so now we need
593 * to look up their jobids.
596 foreach_dlist(item, item_chain) {
597 Dmsg1(000, "Got Job: %s\n", item->item);
598 Mmsg(query, query2, item->item, jcr->pool->hdr.name);
599 Dmsg1(000, "query2=%s\n", query.c_str());
600 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
601 Jmsg(jcr, M_FATAL, 0,
602 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
606 if (ids->count == 0) {
607 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
611 Dmsg2(000, "Count=%d Jobids=%s\n", ids->count, ids->list);
613 Dmsg0(000, "After delete item_chain\n");
619 * Release resources allocated during backup.
621 void migration_cleanup(JCR *jcr, int TermCode)
623 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
624 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
625 char ec6[50], ec7[50], ec8[50];
626 char term_code[100], sd_term_msg[100];
627 const char *term_msg;
632 JCR *prev_jcr = jcr->previous_jcr;
633 POOL_MEM query(PM_MESSAGE);
635 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
636 dequeue_messages(jcr); /* display any queued messages */
637 memset(&mr, 0, sizeof(mr));
638 set_jcr_job_status(jcr, TermCode);
639 update_job_end_record(jcr); /* update database */
642 * Check if we actually did something.
643 * prev_jcr is jcr of the newly migrated job.
646 prev_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
647 prev_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
648 prev_jcr->VolSessionId = jcr->VolSessionId;
649 prev_jcr->VolSessionTime = jcr->VolSessionTime;
650 prev_jcr->jr.RealEndTime = 0;
651 prev_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
653 set_jcr_job_status(prev_jcr, TermCode);
656 update_job_end_record(prev_jcr);
658 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
659 "JobTDate=%s WHERE JobId=%s",
660 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
661 edit_uint64(jcr->previous_jr.JobTDate, ec1),
662 edit_uint64(prev_jcr->jr.JobId, ec2));
663 db_sql_query(prev_jcr->db, query.c_str(), NULL, NULL);
665 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
666 Jmsg(jcr, M_WARNING, 0, _("Error getting job record for stats: %s"),
667 db_strerror(jcr->db));
668 set_jcr_job_status(jcr, JS_ErrorTerminated);
671 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
672 if (!db_get_media_record(jcr, jcr->db, &mr)) {
673 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
674 mr.VolumeName, db_strerror(jcr->db));
675 set_jcr_job_status(jcr, JS_ErrorTerminated);
678 update_bootstrap_file(prev_jcr);
680 if (!db_get_job_volume_names(prev_jcr, prev_jcr->db, prev_jcr->jr.JobId, &prev_jcr->VolumeName)) {
682 * Note, if the job has erred, most likely it did not write any
683 * tape, so suppress this "error" message since in that case
684 * it is normal. Or look at it the other way, only for a
685 * normal exit should we complain about this error.
687 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
688 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(prev_jcr->db));
690 prev_jcr->VolumeName[0] = 0; /* none */
694 msg_type = M_INFO; /* by default INFO message */
695 switch (jcr->JobStatus) {
697 if (jcr->Errors || jcr->SDErrors) {
698 term_msg = _("%s OK -- with warnings");
700 term_msg = _("%s OK");
704 case JS_ErrorTerminated:
705 term_msg = _("*** %s Error ***");
706 msg_type = M_ERROR; /* Generate error message */
707 if (jcr->store_bsock) {
708 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
709 if (jcr->SD_msg_chan) {
710 pthread_cancel(jcr->SD_msg_chan);
715 term_msg = _("%s Canceled");
716 if (jcr->store_bsock) {
717 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
718 if (jcr->SD_msg_chan) {
719 pthread_cancel(jcr->SD_msg_chan);
724 term_msg = _("Inappropriate %s term code");
727 bsnprintf(term_code, sizeof(term_code), term_msg, "Migration");
728 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
729 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
730 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
734 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
738 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
740 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
741 " Prev Backup JobId: %s\n"
742 " New Backup JobId: %s\n"
743 " Migration JobId: %s\n"
744 " Migration Job: %s\n"
745 " Backup Level: %s%s\n"
747 " FileSet: \"%s\" %s\n"
748 " Pool: \"%s\" (From %s)\n"
749 " Storage: \"%s\" (From %s)\n"
752 " Elapsed time: %s\n"
754 " SD Files Written: %s\n"
755 " SD Bytes Written: %s (%sB)\n"
757 " Volume name(s): %s\n"
758 " Volume Session Id: %d\n"
759 " Volume Session Time: %d\n"
760 " Last Volume Bytes: %s (%sB)\n"
762 " SD termination status: %s\n"
763 " Termination: %s\n\n"),
767 prev_jcr ? edit_uint64(jcr->previous_jr.JobId, ec6) : "0",
768 prev_jcr ? edit_uint64(prev_jcr->jr.JobId, ec7) : "0",
769 edit_uint64(jcr->jr.JobId, ec8),
771 level_to_str(jcr->JobLevel), jcr->since,
772 jcr->client->hdr.name,
773 jcr->fileset->hdr.name, jcr->FSCreateTime,
774 jcr->pool->hdr.name, jcr->pool_source,
775 jcr->store->hdr.name, jcr->storage_source,
778 edit_utime(RunTime, elapsed, sizeof(elapsed)),
780 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
781 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
782 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
784 prev_jcr ? prev_jcr->VolumeName : "",
787 edit_uint64_with_commas(mr.VolBytes, ec4),
788 edit_uint64_with_suffix(mr.VolBytes, ec5),
793 Dmsg1(100, "migrate_cleanup() previous_jcr=0x%x\n", jcr->previous_jcr);
794 if (jcr->previous_jcr) {
795 free_jcr(jcr->previous_jcr);
796 jcr->previous_jcr = NULL;
798 Dmsg0(100, "Leave migrate_cleanup()\n");