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);
48 * Called here before the job is run to do the job
51 bool do_migration_init(JCR *jcr)
55 /* If we find a job to migrate it is previous_jr.JobId */
56 if (!get_job_to_migrate(jcr)) {
60 if (jcr->previous_jr.JobId == 0) {
61 return true; /* no work */
64 if (!get_or_create_fileset_record(jcr)) {
69 * Get the Pool record -- first apply any level defined pools
71 switch (jcr->previous_jr.JobLevel) {
74 jcr->pool = jcr->full_pool;
79 jcr->pool = jcr->inc_pool;
84 jcr->pool = jcr->dif_pool;
88 memset(&pr, 0, sizeof(pr));
89 bstrncpy(pr.Name, jcr->pool->hdr.name, sizeof(pr.Name));
91 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
92 /* Try to create the pool */
93 if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
94 Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
95 db_strerror(jcr->db));
98 Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
101 jcr->jr.PoolId = pr.PoolId;
103 /* If pool storage specified, use it instead of job storage */
104 copy_storage(jcr, jcr->pool->storage);
107 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Job or Pool.\n"));
111 if (!create_restore_bootstrap_file(jcr)) {
118 * Do a Migration of a previous job
120 * Returns: false on failure
123 bool do_migration(JCR *jcr)
132 if (jcr->previous_jr.JobId == 0) {
133 set_jcr_job_status(jcr, JS_Terminated);
134 migration_cleanup(jcr, jcr->JobStatus);
135 return true; /* no work */
138 Dmsg4(000, "Previous:: Name=%s JobId=%d Type=%c Level=%c\n",
139 jcr->previous_jr.Name, jcr->previous_jr.JobId,
140 jcr->previous_jr.JobType, jcr->previous_jr.JobLevel);
142 Dmsg4(000, "Current: Name=%s JobId=%d Type=%c Level=%c\n",
143 jcr->jr.Name, jcr->jr.JobId,
144 jcr->jr.JobType, jcr->jr.JobLevel);
147 job = (JOB *)GetResWithName(R_JOB, jcr->jr.Name);
148 prev_job = (JOB *)GetResWithName(R_JOB, jcr->previous_jr.Name);
150 if (!job || !prev_job) {
155 * prev_jcr is the new Job that corresponds to the original
156 * job. It "runs" at the same time as the current
157 * migration job and becomes a new backup job that replaces
158 * the original backup job. Most operations on the current
159 * migration jcr are also done on the prev_jcr.
161 prev_jcr = jcr->previous_jcr = new_jcr(sizeof(JCR), dird_free_jcr);
162 memcpy(&prev_jcr->previous_jr, &jcr->previous_jr, sizeof(prev_jcr->previous_jr));
164 /* Turn the prev_jcr into a "real" job */
165 set_jcr_defaults(prev_jcr, prev_job);
166 if (!setup_job(prev_jcr)) {
169 /* Set output PoolId and FileSetId. */
170 prev_jcr->jr.PoolId = jcr->jr.PoolId;
171 prev_jcr->jr.FileSetId = jcr->jr.FileSetId;
174 * Get the PoolId used with the original job. Then
175 * find the pool name from the database record.
177 memset(&pr, 0, sizeof(pr));
178 pr.PoolId = prev_jcr->previous_jr.PoolId;
179 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
180 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
181 edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
184 /* Get the pool resource corresponding to the original job */
185 pool = (POOL *)GetResWithName(R_POOL, pr.Name);
187 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
191 /* Check Migration time and High/Low water marks */
194 /* If pool storage specified, use it for restore */
195 copy_storage(prev_jcr, pool->storage);
197 /* If the original backup pool has a NextPool, make sure a
198 * record exists in the database.
200 if (pool->NextPool) {
201 memset(&pr, 0, sizeof(pr));
202 bstrncpy(pr.Name, pool->NextPool->hdr.name, sizeof(pr.Name));
204 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
205 /* Try to create the pool */
206 if (create_pool(jcr, jcr->db, pool->NextPool, POOL_OP_CREATE) < 0) {
207 Jmsg(jcr, M_FATAL, 0, _("Pool \"%s\" not in database. %s"), pr.Name,
208 db_strerror(jcr->db));
211 Jmsg(jcr, M_INFO, 0, _("Pool \"%s\" created in database.\n"), pr.Name);
215 * put the "NextPool" resource pointer in our jcr so that we
216 * can pull the Storage reference from it.
218 prev_jcr->pool = jcr->pool = pool->NextPool;
219 prev_jcr->jr.PoolId = jcr->jr.PoolId = pr.PoolId;
222 /* If pool storage specified, use it instead of job storage for backup */
223 copy_storage(jcr, jcr->pool->storage);
225 /* Print Job Start message */
226 Jmsg(jcr, M_INFO, 0, _("Start Migration JobId %s, Job=%s\n"),
227 edit_uint64(jcr->JobId, ed1), jcr->Job);
229 set_jcr_job_status(jcr, JS_Running);
230 set_jcr_job_status(prev_jcr, JS_Running);
231 Dmsg2(000, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
232 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
233 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
237 if (!db_update_job_start_record(prev_jcr, prev_jcr->db, &prev_jcr->jr)) {
238 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(prev_jcr->db));
244 * Open a message channel connection with the Storage
245 * daemon. This is to let him know that our client
246 * will be contacting him for a backup session.
249 Dmsg0(110, "Open connection with storage daemon\n");
250 set_jcr_job_status(jcr, JS_WaitSD);
251 set_jcr_job_status(prev_jcr, JS_WaitSD);
253 * Start conversation with Storage daemon
255 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
258 sd = jcr->store_bsock;
260 * Now start a job with the Storage daemon
262 Dmsg2(000, "Read store=%s, write store=%s\n",
263 ((STORE *)prev_jcr->storage->first())->hdr.name,
264 ((STORE *)jcr->storage->first())->hdr.name);
265 if (!start_storage_daemon_job(jcr, prev_jcr->storage, jcr->storage)) {
268 Dmsg0(150, "Storage daemon connection OK\n");
270 if (!send_bootstrap_file(jcr, sd) ||
271 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
276 * Now start a Storage daemon message thread
278 if (!start_storage_daemon_message_thread(jcr)) {
282 if (!bnet_fsend(sd, "run")) {
286 set_jcr_job_status(jcr, JS_Running);
287 set_jcr_job_status(prev_jcr, JS_Running);
289 /* Pickup Job termination data */
290 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
291 wait_for_storage_daemon_termination(jcr);
293 set_jcr_job_status(jcr, jcr->SDJobStatus);
294 if (jcr->JobStatus == JS_Terminated) {
295 migration_cleanup(jcr, jcr->JobStatus);
307 * Callback handler make list of DB Ids
309 static int dbid_handler(void *ctx, int num_fields, char **row)
311 idpkt *ids = (idpkt *)ctx;
313 Dmsg3(000, "count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
314 if (ids->count == 0) {
317 pm_strcat(ids->list, ",");
319 pm_strcat(ids->list, row[0]);
330 static int item_compare(void *item1, void *item2)
332 uitem *i1 = (uitem *)item1;
333 uitem *i2 = (uitem *)item2;
334 return strcmp(i1->item, i2->item);
337 static int unique_name_handler(void *ctx, int num_fields, char **row)
339 dlist *list = (dlist *)ctx;
341 uitem *new_item = (uitem *)malloc(sizeof(uitem));
344 memset(new_item, 0, sizeof(uitem));
345 new_item->item = bstrdup(row[0]);
346 Dmsg1(000, "Item=%s\n", row[0]);
347 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
348 if (item != new_item) { /* already in list */
349 free(new_item->item);
350 free((char *)new_item);
356 /* Get Job names in Pool */
357 const char *sql_job =
358 "SELECT DISTINCT Job.Name from Job,Pool"
359 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
361 /* Get JobIds from regex'ed Job names */
362 const char *sql_jobids_from_job =
363 "SELECT DISTINCT Job.JobId FROM Job,Pool"
364 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
365 " ORDER by Job.StartTime";
367 /* Get Client names in Pool */
368 const char *sql_client =
369 "SELECT DISTINCT Client.Name from Client,Pool,Job"
370 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
371 " Job.PoolId=Pool.PoolId";
373 /* Get JobIds from regex'ed Client names */
374 const char *sql_jobids_from_client =
375 "SELECT DISTINCT Job.JobId FROM Job,Pool"
376 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
377 " AND Job.ClientId=Client.ClientId "
378 " ORDER by Job.StartTime";
380 /* Get Volume names in Pool */
381 const char *sql_vol =
382 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
383 " VolStatus in ('Full','Used','Error') AND"
384 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
386 /* Get JobIds from regex'ed Volume names */
387 const char *sql_jobids_from_vol =
388 "SELECT DISTINCT Job.JobId FROM Media,JobMedia,Job"
389 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
390 " AND JobMedia.JobId=Job.JobId"
391 " ORDER by Job.StartTime";
397 const char *sql_smallest_vol =
398 "SELECT MediaId FROM Media,Pool WHERE"
399 " VolStatus in ('Full','Used','Error') AND"
400 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
401 " ORDER BY VolBytes ASC LIMIT 1";
403 const char *sql_oldest_vol =
404 "SELECT MediaId FROM Media,Pool WHERE"
405 " VolStatus in ('Full','Used','Error') AND"
406 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
407 " ORDER BY LastWritten ASC LIMIT 1";
409 const char *sql_jobids_from_mediaid =
410 "SELECT DISTINCT Job.JobId FROM JobMedia,Job"
411 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId=%s"
412 " ORDER by Job.StartTime";
414 const char *sql_pool_bytes =
415 "SELECT SUM(VolBytes) FROM Media,Pool WHERE"
416 " VolStatus in ('Full','Used','Error','Append') AND"
417 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
419 const char *sql_vol_bytes =
420 "SELECT MediaId FROM Media,Pool WHERE"
421 " VolStatus in ('Full','Used','Error') AND"
422 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
423 " VolBytes<%s ORDER BY LastWritten ASC LIMIT 1";
426 const char *sql_ujobid =
427 "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
428 " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
429 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
434 * Returns: false on error
435 * true if OK and jcr->previous_jr filled in
437 static bool get_job_to_migrate(JCR *jcr)
440 POOL_MEM query(PM_MESSAGE);
446 ids.list = get_pool_memory(PM_MESSAGE);
447 Dmsg1(000, "list=%p\n", ids.list);
451 if (jcr->MigrateJobId != 0) {
452 jcr->previous_jr.JobId = jcr->MigrateJobId;
453 Dmsg1(000, "previous jobid=%u\n", jcr->MigrateJobId);
455 switch (jcr->job->selection_type) {
457 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
462 if (!regex_find_jobids(jcr, &ids, sql_client,
463 sql_jobids_from_client, "Client")) {
468 if (!regex_find_jobids(jcr, &ids, sql_vol,
469 sql_jobids_from_vol, "Volume")) {
474 if (!jcr->job->selection_pattern) {
475 Jmsg(jcr, M_FATAL, 0, _("No Migration SQL selection pattern specified.\n"));
478 Dmsg1(000, "SQL=%s\n", jcr->job->selection_pattern);
479 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
480 dbid_handler, (void *)&ids)) {
481 Jmsg(jcr, M_FATAL, 0,
482 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
488 /***** Below not implemented yet *********/
489 case MT_SMALLEST_VOL:
490 Mmsg(query, sql_smallest_vol, jcr->pool->hdr.name);
491 // Mmsg(query2, sql_jobids_from_mediaid, JobIds);
492 // Dmsg1(000, "Smallest Vol Jobids=%s\n", JobIds);
495 Mmsg(query, sql_oldest_vol, jcr->pool->hdr.name);
496 // Mmsg(query2, sql_jobids_from_mediaid, JobIds);
497 // Dmsg1(000, "Oldest Vol Jobids=%s\n", JobIds);
499 case MT_POOL_OCCUPANCY:
500 Mmsg(query, sql_pool_bytes, jcr->pool->hdr.name);
501 // Dmsg1(000, "Pool Occupancy Jobids=%s\n", JobIds);
504 Dmsg0(000, "Pool time not implemented\n");
507 Jmsg(jcr, M_FATAL, 0, _("Unknown Migration Selection Type.\n"));
514 stat = get_next_jobid_from_list(&p, &JobId);
515 Dmsg2(000, "get_next_jobid stat=%d JobId=%u\n", stat, JobId);
517 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
519 } else if (stat == 0) {
520 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
524 jcr->previous_jr.JobId = JobId;
525 Dmsg1(000, "Last jobid=%d\n", jcr->previous_jr.JobId);
527 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
528 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to migrate. ERR=%s"),
529 edit_int64(jcr->previous_jr.JobId, ed1),
530 db_strerror(jcr->db));
533 Jmsg(jcr, M_INFO, 0, _("Migration using JobId=%d Job=%s\n"),
534 jcr->previous_jr.JobId, jcr->previous_jr.Job);
537 free_pool_memory(ids.list);
541 free_pool_memory(ids.list);
546 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
547 const char *query2, const char *type) {
550 uitem *last_item = NULL;
555 POOL_MEM query(PM_MESSAGE);
557 item_chain = New(dlist(item, &item->link));
558 if (!jcr->job->selection_pattern) {
559 Jmsg(jcr, M_FATAL, 0, _("No Migration %s selection pattern specified.\n"),
563 Dmsg1(000, "regex=%s\n", jcr->job->selection_pattern);
564 /* Compile regex expression */
565 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
567 regerror(rc, &preg, prbuf, sizeof(prbuf));
568 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
569 jcr->job->selection_pattern, prbuf);
572 /* Basic query for names */
573 Mmsg(query, query1, jcr->pool->hdr.name);
574 Dmsg1(000, "query1=%s\n", query.c_str());
575 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
576 (void *)item_chain)) {
577 Jmsg(jcr, M_FATAL, 0,
578 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
581 /* Now apply the regex to the names and remove any item not matched */
582 foreach_dlist(item, item_chain) {
583 const int nmatch = 30;
584 regmatch_t pmatch[nmatch];
586 Dmsg1(000, "Remove item %s\n", last_item->item);
587 free(last_item->item);
588 item_chain->remove(last_item);
590 Dmsg1(000, "Jobitem=%s\n", item->item);
591 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
593 last_item = NULL; /* keep this one */
599 free(last_item->item);
600 Dmsg1(000, "Remove item %s\n", last_item->item);
601 item_chain->remove(last_item);
605 * At this point, we have a list of items in item_chain
606 * that have been matched by the regex, so now we need
607 * to look up their jobids.
610 foreach_dlist(item, item_chain) {
611 Dmsg1(000, "Got Job: %s\n", item->item);
612 Mmsg(query, query2, item->item, jcr->pool->hdr.name);
613 Dmsg1(000, "query2=%s\n", query.c_str());
614 if (!db_sql_query(jcr->db, query.c_str(), dbid_handler, (void *)ids)) {
615 Jmsg(jcr, M_FATAL, 0,
616 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
620 if (ids->count == 0) {
621 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
625 Dmsg2(000, "Count=%d Jobids=%s\n", ids->count, ids->list);
627 Dmsg0(000, "After delete item_chain\n");
633 * Release resources allocated during backup.
635 void migration_cleanup(JCR *jcr, int TermCode)
637 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
638 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
639 char ec6[50], ec7[50], ec8[50];
640 char term_code[100], sd_term_msg[100];
641 const char *term_msg;
646 JCR *prev_jcr = jcr->previous_jcr;
647 POOL_MEM query(PM_MESSAGE);
649 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
650 dequeue_messages(jcr); /* display any queued messages */
651 memset(&mr, 0, sizeof(mr));
652 set_jcr_job_status(jcr, TermCode);
653 update_job_end_record(jcr); /* update database */
655 /* Check if we actually did something */
657 prev_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
658 prev_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
659 prev_jcr->VolSessionId = jcr->VolSessionId;
660 prev_jcr->VolSessionTime = jcr->VolSessionTime;
662 set_jcr_job_status(prev_jcr, TermCode);
664 update_job_end_record(prev_jcr);
666 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
667 "JobTDate=%s WHERE JobId=%s",
668 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
669 edit_uint64(jcr->previous_jr.JobTDate, ec1),
670 edit_uint64(prev_jcr->jr.JobId, ec2));
671 db_sql_query(prev_jcr->db, query.c_str(), NULL, NULL);
673 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
674 Jmsg(jcr, M_WARNING, 0, _("Error getting job record for stats: %s"),
675 db_strerror(jcr->db));
676 set_jcr_job_status(jcr, JS_ErrorTerminated);
679 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
680 if (!db_get_media_record(jcr, jcr->db, &mr)) {
681 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
682 mr.VolumeName, db_strerror(jcr->db));
683 set_jcr_job_status(jcr, JS_ErrorTerminated);
686 update_bootstrap_file(prev_jcr);
688 if (!db_get_job_volume_names(prev_jcr, prev_jcr->db, prev_jcr->jr.JobId, &prev_jcr->VolumeName)) {
690 * Note, if the job has erred, most likely it did not write any
691 * tape, so suppress this "error" message since in that case
692 * it is normal. Or look at it the other way, only for a
693 * normal exit should we complain about this error.
695 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
696 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(prev_jcr->db));
698 prev_jcr->VolumeName[0] = 0; /* none */
702 msg_type = M_INFO; /* by default INFO message */
703 switch (jcr->JobStatus) {
705 if (jcr->Errors || jcr->SDErrors) {
706 term_msg = _("%s OK -- with warnings");
708 term_msg = _("%s OK");
712 case JS_ErrorTerminated:
713 term_msg = _("*** %s Error ***");
714 msg_type = M_ERROR; /* Generate error message */
715 if (jcr->store_bsock) {
716 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
717 if (jcr->SD_msg_chan) {
718 pthread_cancel(jcr->SD_msg_chan);
723 term_msg = _("%s Canceled");
724 if (jcr->store_bsock) {
725 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
726 if (jcr->SD_msg_chan) {
727 pthread_cancel(jcr->SD_msg_chan);
732 term_msg = _("Inappropriate %s term code");
735 bsnprintf(term_code, sizeof(term_code), term_msg, "Migration");
736 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
737 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
738 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
742 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
746 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
748 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
749 " Prev Backup JobId: %s\n"
750 " New Backup JobId: %s\n"
751 " Migration JobId: %s\n"
752 " Migration Job: %s\n"
753 " Backup Level: %s%s\n"
755 " FileSet: \"%s\" %s\n"
759 " Elapsed time: %s\n"
761 " SD Files Written: %s\n"
762 " SD Bytes Written: %s (%sB)\n"
764 " Volume name(s): %s\n"
765 " Volume Session Id: %d\n"
766 " Volume Session Time: %d\n"
767 " Last Volume Bytes: %s (%sB)\n"
769 " SD termination status: %s\n"
770 " Termination: %s\n\n"),
774 prev_jcr ? edit_uint64(jcr->previous_jr.JobId, ec6) : "0",
775 prev_jcr ? edit_uint64(prev_jcr->jr.JobId, ec7) : "0",
776 edit_uint64(jcr->jr.JobId, ec8),
778 level_to_str(jcr->JobLevel), jcr->since,
779 jcr->client->hdr.name,
780 jcr->fileset->hdr.name, jcr->FSCreateTime,
784 edit_utime(RunTime, elapsed, sizeof(elapsed)),
786 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
787 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
788 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
790 prev_jcr ? prev_jcr->VolumeName : "",
793 edit_uint64_with_commas(mr.VolBytes, ec4),
794 edit_uint64_with_suffix(mr.VolBytes, ec5),
799 Dmsg1(100, "migrate_cleanup() previous_jcr=0x%x\n", jcr->previous_jcr);
800 if (jcr->previous_jcr) {
801 free_jcr(jcr->previous_jcr);
802 jcr->previous_jcr = NULL;
804 Dmsg0(100, "Leave migrate_cleanup()\n");