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, jobitems *ji, 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 JobIds
309 static int jobid_handler(void *ctx, int num_fields, char **row)
311 jobitems *ji = (jobitems *)ctx;
313 if (ji->count == 0) {
316 pm_strcat(ji->JobIds, ",");
318 pm_strcat(ji->JobIds, row[0]);
329 static int item_compare(void *item1, void *item2)
331 uitem *i1 = (uitem *)item1;
332 uitem *i2 = (uitem *)item2;
333 return strcmp(i1->item, i2->item);
336 static int unique_name_handler(void *ctx, int num_fields, char **row)
338 dlist *list = (dlist *)ctx;
340 uitem *new_item = (uitem *)malloc(sizeof(uitem));
343 memset(new_item, 0, sizeof(uitem));
344 new_item->item = bstrdup(row[0]);
345 Dmsg1(000, "Item=%s\n", row[0]);
346 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
347 if (item != new_item) { /* already in list */
348 free(new_item->item);
349 free((char *)new_item);
355 /* Get Job names in Pool */
356 const char *sql_job =
357 "SELECT DISTINCT Job.Name from Job,Pool"
358 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
360 /* Get JobIds from regex'ed Job names */
361 const char *sql_jobids_from_job =
362 "SELECT DISTINCT Job.JobId FROM Job,Pool"
363 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
364 " ORDER by Job.StartTime";
366 /* Get Client names in Pool */
367 const char *sql_client =
368 "SELECT DISTINCT Client.Name from Client,Pool,Job"
369 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
370 " Job.PoolId=Pool.PoolId";
372 /* Get JobIds from regex'ed Client names */
373 const char *sql_jobids_from_client =
374 "SELECT DISTINCT Job.JobId FROM Job,Pool"
375 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
376 " AND Job.ClientId=Client.ClientId "
377 " ORDER by Job.StartTime";
379 /* Get Volume names in Pool */
380 const char *sql_vol =
381 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
382 " VolStatus in ('Full','Used','Error') AND"
383 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
385 /* Get JobIds from regex'ed Volume names */
386 const char *sql_jobids_from_vol =
387 "SELECT DISTINCT Job.JobId FROM Media,JobMedia,Job"
388 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
389 " AND JobMedia.JobId=Job.JobId"
390 " ORDER by Job.StartTime";
396 const char *sql_smallest_vol =
397 "SELECT MediaId FROM Media,Pool WHERE"
398 " VolStatus in ('Full','Used','Error') AND"
399 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
400 " ORDER BY VolBytes ASC LIMIT 1";
402 const char *sql_oldest_vol =
403 "SELECT MediaId FROM Media,Pool WHERE"
404 " VolStatus in ('Full','Used','Error') AND"
405 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
406 " ORDER BY LastWritten ASC LIMIT 1";
408 const char *sql_jobids_from_mediaid =
409 "SELECT DISTINCT Job.JobId FROM JobMedia,Job"
410 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId=%s"
411 " ORDER by Job.StartTime";
413 const char *sql_pool_bytes =
414 "SELECT SUM(VolBytes) FROM Media,Pool WHERE"
415 " VolStatus in ('Full','Used','Error','Append') AND"
416 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
418 const char *sql_vol_bytes =
419 "SELECT MediaId FROM Media,Pool WHERE"
420 " VolStatus in ('Full','Used','Error') AND"
421 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
422 " VolBytes<%s ORDER BY LastWritten ASC LIMIT 1";
425 const char *sql_ujobid =
426 "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
427 " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
428 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
433 * Returns: false on error
434 * true if OK and jcr->previous_jr filled in
436 static bool get_job_to_migrate(JCR *jcr)
439 POOL_MEM query(PM_MESSAGE);
445 ji.JobIds = get_pool_memory(PM_MESSAGE);
448 if (jcr->MigrateJobId != 0) {
449 jcr->previous_jr.JobId = jcr->MigrateJobId;
450 Dmsg1(000, "previous jobid=%u\n", jcr->MigrateJobId);
452 switch (jcr->job->selection_type) {
454 if (!regex_find_jobids(jcr, &ji, sql_job, sql_jobids_from_job, "Job")) {
459 if (!regex_find_jobids(jcr, &ji, sql_client,
460 sql_jobids_from_client, "Client")) {
465 if (!regex_find_jobids(jcr, &ji, sql_vol,
466 sql_jobids_from_vol, "Volume")) {
471 if (!jcr->job->selection_pattern) {
472 Jmsg(jcr, M_FATAL, 0, _("No Migration SQL selection pattern specified.\n"));
475 Dmsg1(000, "SQL=%s\n", jcr->job->selection_pattern);
476 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
477 jobid_handler, (void *)&ji)) {
478 Jmsg(jcr, M_FATAL, 0,
479 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
485 /***** Below not implemented yet *********/
486 case MT_SMALLEST_VOL:
487 Mmsg(query, sql_smallest_vol, jcr->pool->hdr.name);
488 // Mmsg(query2, sql_jobids_from_mediaid, JobIds);
489 // Dmsg1(000, "Smallest Vol Jobids=%s\n", JobIds);
492 Mmsg(query, sql_oldest_vol, jcr->pool->hdr.name);
493 // Mmsg(query2, sql_jobids_from_mediaid, JobIds);
494 // Dmsg1(000, "Oldest Vol Jobids=%s\n", JobIds);
496 case MT_POOL_OCCUPANCY:
497 Mmsg(query, sql_pool_bytes, jcr->pool->hdr.name);
498 // Dmsg1(000, "Pool Occupancy Jobids=%s\n", JobIds);
501 Dmsg0(000, "Pool time not implemented\n");
504 Jmsg(jcr, M_FATAL, 0, _("Unknown Migration Selection Type.\n"));
511 stat = get_next_jobid_from_list(&p, &JobId);
512 Dmsg2(000, "get_next_jobid stat=%d JobId=%u\n", stat, JobId);
514 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
516 } else if (stat == 0) {
517 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
521 jcr->previous_jr.JobId = JobId;
522 Dmsg1(000, "Last jobid=%d\n", jcr->previous_jr.JobId);
524 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
525 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to migrate. ERR=%s"),
526 edit_int64(jcr->previous_jr.JobId, ed1),
527 db_strerror(jcr->db));
530 Jmsg(jcr, M_INFO, 0, _("Migration using JobId=%d Job=%s\n"),
531 jcr->previous_jr.JobId, jcr->previous_jr.Job);
534 free_pool_memory(ji.JobIds);
538 free_pool_memory(ji.JobIds);
543 static bool regex_find_jobids(JCR *jcr, jobitems *ji, const char *query1,
544 const char *query2, const char *type) {
547 uitem *last_item = NULL;
552 POOL_MEM query(PM_MESSAGE);
554 item_chain = New(dlist(item, &item->link));
555 if (!jcr->job->selection_pattern) {
556 Jmsg(jcr, M_FATAL, 0, _("No Migration %s selection pattern specified.\n"),
560 Dmsg1(000, "regex=%s\n", jcr->job->selection_pattern);
561 /* Compile regex expression */
562 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
564 regerror(rc, &preg, prbuf, sizeof(prbuf));
565 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
566 jcr->job->selection_pattern, prbuf);
569 /* Basic query for names */
570 Mmsg(query, query1, jcr->pool->hdr.name);
571 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
572 (void *)item_chain)) {
573 Jmsg(jcr, M_FATAL, 0,
574 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
577 /* Now apply the regex to the names and remove any item not matched */
578 foreach_dlist(item, item_chain) {
579 const int nmatch = 30;
580 regmatch_t pmatch[nmatch];
582 Dmsg1(000, "Remove item %s\n", last_item->item);
583 free(last_item->item);
584 item_chain->remove(last_item);
586 Dmsg1(000, "Jobitem=%s\n", item->item);
587 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
589 last_item = NULL; /* keep this one */
595 free(last_item->item);
596 Dmsg1(000, "Remove item %s\n", last_item->item);
597 item_chain->remove(last_item);
601 * At this point, we have a list of items in item_chain
602 * that have been matched by the regex, so now we need
603 * to look up their jobids.
606 foreach_dlist(item, item_chain) {
607 Dmsg1(000, "Got Job: %s\n", item->item);
608 Mmsg(query, query2, item->item, jcr->pool->hdr.name);
609 if (!db_sql_query(jcr->db, query.c_str(), jobid_handler, (void *)&ji)) {
610 Jmsg(jcr, M_FATAL, 0,
611 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
615 if (ji->count == 0) {
616 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
620 Dmsg1(000, "Job Jobids=%s\n", ji->JobIds);
627 * Release resources allocated during backup.
629 void migration_cleanup(JCR *jcr, int TermCode)
631 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
632 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
633 char term_code[100], sd_term_msg[100];
634 const char *term_msg;
639 JCR *prev_jcr = jcr->previous_jcr;
640 POOL_MEM query(PM_MESSAGE);
642 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
643 dequeue_messages(jcr); /* display any queued messages */
644 memset(&mr, 0, sizeof(mr));
645 set_jcr_job_status(jcr, TermCode);
646 update_job_end_record(jcr); /* update database */
648 /* Check if we actually did something */
650 prev_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
651 prev_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
652 prev_jcr->VolSessionId = jcr->VolSessionId;
653 prev_jcr->VolSessionTime = jcr->VolSessionTime;
655 set_jcr_job_status(prev_jcr, TermCode);
657 update_job_end_record(prev_jcr);
659 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
660 "JobTDate=%s WHERE JobId=%s",
661 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
662 edit_uint64(jcr->previous_jr.JobTDate, ec1),
663 edit_uint64(prev_jcr->jr.JobId, ec2));
664 db_sql_query(prev_jcr->db, query.c_str(), NULL, NULL);
666 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
667 Jmsg(jcr, M_WARNING, 0, _("Error getting job record for stats: %s"),
668 db_strerror(jcr->db));
669 set_jcr_job_status(jcr, JS_ErrorTerminated);
672 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
673 if (!db_get_media_record(jcr, jcr->db, &mr)) {
674 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
675 mr.VolumeName, db_strerror(jcr->db));
676 set_jcr_job_status(jcr, JS_ErrorTerminated);
679 update_bootstrap_file(prev_jcr);
681 if (!db_get_job_volume_names(prev_jcr, prev_jcr->db, prev_jcr->jr.JobId, &prev_jcr->VolumeName)) {
683 * Note, if the job has erred, most likely it did not write any
684 * tape, so suppress this "error" message since in that case
685 * it is normal. Or look at it the other way, only for a
686 * normal exit should we complain about this error.
688 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
689 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(prev_jcr->db));
691 prev_jcr->VolumeName[0] = 0; /* none */
695 msg_type = M_INFO; /* by default INFO message */
696 switch (jcr->JobStatus) {
698 if (jcr->Errors || jcr->SDErrors) {
699 term_msg = _("%s OK -- with warnings");
701 term_msg = _("%s OK");
705 case JS_ErrorTerminated:
706 term_msg = _("*** %s Error ***");
707 msg_type = M_ERROR; /* Generate error message */
708 if (jcr->store_bsock) {
709 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
710 if (jcr->SD_msg_chan) {
711 pthread_cancel(jcr->SD_msg_chan);
716 term_msg = _("%s Canceled");
717 if (jcr->store_bsock) {
718 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
719 if (jcr->SD_msg_chan) {
720 pthread_cancel(jcr->SD_msg_chan);
725 term_msg = _("Inappropriate %s term code");
728 bsnprintf(term_code, sizeof(term_code), term_msg, "Migration");
729 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
730 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
731 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
735 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
739 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
741 // bmicrosleep(15, 0); /* for debugging SIGHUP */
743 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
744 " Old Backup JobId: %u\n"
745 " New Backup JobId: %u\n"
748 " Backup Level: %s%s\n"
750 " FileSet: \"%s\" %s\n"
754 " Elapsed time: %s\n"
756 " SD Files Written: %s\n"
757 " SD Bytes Written: %s (%sB)\n"
759 " Volume name(s): %s\n"
760 " Volume Session Id: %d\n"
761 " Volume Session Time: %d\n"
762 " Last Volume Bytes: %s (%sB)\n"
764 " SD termination status: %s\n"
765 " Termination: %s\n\n"),
769 prev_jcr ? jcr->previous_jr.JobId : 0,
770 prev_jcr ? prev_jcr->jr.JobId : 0,
773 level_to_str(jcr->JobLevel), jcr->since,
774 jcr->client->hdr.name,
775 jcr->fileset->hdr.name, jcr->FSCreateTime,
779 edit_utime(RunTime, elapsed, sizeof(elapsed)),
781 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
782 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
783 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
785 prev_jcr ? prev_jcr->VolumeName : "",
788 edit_uint64_with_commas(mr.VolBytes, ec4),
789 edit_uint64_with_suffix(mr.VolBytes, ec5),
794 Dmsg1(000, "migrate_cleanup() previous_jcr=0x%x\n", jcr->previous_jcr);
795 if (jcr->previous_jcr) {
796 // free_jcr(jcr->previous_jcr);
797 // jcr->previous_jcr = NULL;
799 Dmsg0(000, "Leave migrate_cleanup()\n");