]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/migrate.c
- Add 'Dir Status' button to the gnome console.
[bacula/bacula] / bacula / src / dird / migrate.c
1 /*
2  *
3  *   Bacula Director -- migrate.c -- responsible for doing
4  *     migration jobs.
5  *
6  *     Kern Sibbald, September MMIV
7  *
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
12  *       to do the backup.
13  *     When the Storage daemon finishes the job, update the DB.
14  *
15  *   Version $Id$
16  */
17 /*
18    Copyright (C) 2004-2006 Kern Sibbald
19
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.
24
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.
29
30  */
31
32 #include "bacula.h"
33 #include "dird.h"
34 #include "ua.h"
35 #ifndef HAVE_REGEX_H
36 #include "lib/bregex.h"
37 #else
38 #include <regex.h>
39 #endif
40
41 static char OKbootstrap[] = "3000 OK bootstrap\n";
42 static bool get_job_to_migrate(JCR *jcr);
43 struct jobitems;
44 static bool regex_find_jobids(JCR *jcr, jobitems *ji, const char *query1,
45                  const char *query2, const char *type);
46
47 /* 
48  * Called here before the job is run to do the job
49  *   specific setup.
50  */
51 bool do_migration_init(JCR *jcr)
52 {
53    POOL_DBR pr;
54
55    /* If we find a job to migrate it is previous_jr.JobId */
56    if (!get_job_to_migrate(jcr)) {
57       return false;
58    }
59
60    if (jcr->previous_jr.JobId == 0) {
61       return true;                    /* no work */
62    }
63
64    if (!get_or_create_fileset_record(jcr)) {
65       return false;
66    }
67
68    /*
69     * Get the Pool record -- first apply any level defined pools
70     */
71    switch (jcr->previous_jr.JobLevel) {
72    case L_FULL:
73       if (jcr->full_pool) {
74          jcr->pool = jcr->full_pool;
75       }
76       break;
77    case L_INCREMENTAL:
78       if (jcr->inc_pool) {
79          jcr->pool = jcr->inc_pool;
80       }
81       break;
82    case L_DIFFERENTIAL:
83       if (jcr->dif_pool) {
84          jcr->pool = jcr->dif_pool;
85       }
86       break;
87    }
88    memset(&pr, 0, sizeof(pr));
89    bstrncpy(pr.Name, jcr->pool->hdr.name, sizeof(pr.Name));
90
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));
96          return false;
97       } else {
98          Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
99       }
100    }
101    jcr->jr.PoolId = pr.PoolId;
102
103    /* If pool storage specified, use it instead of job storage */
104    copy_storage(jcr, jcr->pool->storage);
105
106    if (!jcr->storage) {
107       Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Job or Pool.\n"));
108       return false;
109    }
110
111    if (!create_restore_bootstrap_file(jcr)) {
112       return false;
113    }
114    return true;
115 }
116
117 /*
118  * Do a Migration of a previous job
119  *
120  *  Returns:  false on failure
121  *            true  on success
122  */
123 bool do_migration(JCR *jcr)
124 {
125    POOL_DBR pr;
126    POOL *pool;
127    char ed1[100];
128    BSOCK *sd;
129    JOB *job, *prev_job;
130    JCR *prev_jcr;
131
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 */
136    }
137
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);
141
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);
145
146    LockRes();
147    job = (JOB *)GetResWithName(R_JOB, jcr->jr.Name);
148    prev_job = (JOB *)GetResWithName(R_JOB, jcr->previous_jr.Name);
149    UnlockRes();
150    if (!job || !prev_job) {
151       return false;
152    }
153
154    /* 
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.
160     */
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));
163
164    /* Turn the prev_jcr into a "real" job */
165    set_jcr_defaults(prev_jcr, prev_job);
166    if (!setup_job(prev_jcr)) {
167       return false;
168    }
169    /* Set output PoolId and FileSetId. */
170    prev_jcr->jr.PoolId = jcr->jr.PoolId;
171    prev_jcr->jr.FileSetId = jcr->jr.FileSetId;
172
173    /*
174     * Get the PoolId used with the original job. Then
175     *  find the pool name from the database record.
176     */
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));
182          return false;
183    }
184    /* Get the pool resource corresponding to the original job */
185    pool = (POOL *)GetResWithName(R_POOL, pr.Name);
186    if (!pool) {
187       Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
188       return false;
189    }
190
191    /* Check Migration time and High/Low water marks */
192    /* ***FIXME*** */
193
194    /* If pool storage specified, use it for restore */
195    copy_storage(prev_jcr, pool->storage);
196
197    /* If the original backup pool has a NextPool, make sure a 
198     *  record exists in the database.
199     */
200    if (pool->NextPool) {
201       memset(&pr, 0, sizeof(pr));
202       bstrncpy(pr.Name, pool->NextPool->hdr.name, sizeof(pr.Name));
203
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));
209             return false;
210          } else {
211             Jmsg(jcr, M_INFO, 0, _("Pool \"%s\" created in database.\n"), pr.Name);
212          }
213       }
214       /*
215        * put the "NextPool" resource pointer in our jcr so that we
216        * can pull the Storage reference from it.
217        */
218       prev_jcr->pool = jcr->pool = pool->NextPool;
219       prev_jcr->jr.PoolId = jcr->jr.PoolId = pr.PoolId;
220    }
221
222    /* If pool storage specified, use it instead of job storage for backup */
223    copy_storage(jcr, jcr->pool->storage);
224
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);
228
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));
234       return false;
235    }
236
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));
239       return false;
240    }
241
242
243    /*
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.
247     *
248     */
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);
252    /*
253     * Start conversation with Storage daemon
254     */
255    if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
256       return false;
257    }
258    sd = jcr->store_bsock;
259    /*
260     * Now start a job with the Storage daemon
261     */
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)) {
266       return false;
267    }
268    Dmsg0(150, "Storage daemon connection OK\n");
269
270    if (!send_bootstrap_file(jcr, sd) ||
271        !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
272       return false;
273    }
274
275    /*
276     * Now start a Storage daemon message thread
277     */
278    if (!start_storage_daemon_message_thread(jcr)) {
279       return false;
280    }
281
282    if (!bnet_fsend(sd, "run")) {
283       return false;
284    }
285
286    set_jcr_job_status(jcr, JS_Running);
287    set_jcr_job_status(prev_jcr, JS_Running);
288
289    /* Pickup Job termination data */
290    /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
291    wait_for_storage_daemon_termination(jcr);
292
293    set_jcr_job_status(jcr, jcr->SDJobStatus);
294    if (jcr->JobStatus == JS_Terminated) {
295       migration_cleanup(jcr, jcr->JobStatus);
296       return true;
297    }
298    return false;
299 }
300
301 struct jobitems {
302    POOLMEM *JobIds;
303    uint32_t count;
304 };
305
306 /*
307  * Callback handler make list of JobIds
308  */
309 static int jobid_handler(void *ctx, int num_fields, char **row)
310 {
311    jobitems *ji = (jobitems *)ctx;
312
313    if (ji->count == 0) {
314       ji->JobIds[0] = 0;
315    } else {
316       pm_strcat(ji->JobIds, ",");
317    }
318    pm_strcat(ji->JobIds, row[0]);
319    ji->count++;
320    return 0;
321 }
322
323
324 struct uitem {
325    dlink link;   
326    char *item;
327 };
328
329 static int item_compare(void *item1, void *item2)
330 {
331    uitem *i1 = (uitem *)item1;
332    uitem *i2 = (uitem *)item2;
333    return strcmp(i1->item, i2->item);
334 }
335
336 static int unique_name_handler(void *ctx, int num_fields, char **row)
337 {
338    dlist *list = (dlist *)ctx;
339
340    uitem *new_item = (uitem *)malloc(sizeof(uitem));
341    uitem *item;
342    
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);
350       return 0;
351    }
352    return 0;
353 }
354
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";
359
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";
365
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";
371
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";
378
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'";
384
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";
391    
392
393
394
395
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";
401
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";
407
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";
412
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'";
417
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";
423
424
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";
429
430
431
432 /*
433  * Returns: false on error
434  *          true  if OK and jcr->previous_jr filled in
435  */
436 static bool get_job_to_migrate(JCR *jcr)
437 {
438    char ed1[30];
439    POOL_MEM query(PM_MESSAGE);
440    JobId_t JobId;
441    int stat;
442    char *p;
443    jobitems ji;
444
445    ji.JobIds = get_pool_memory(PM_MESSAGE);
446
447    ji.count = 0;
448    if (jcr->MigrateJobId != 0) {
449       jcr->previous_jr.JobId = jcr->MigrateJobId;
450       Dmsg1(000, "previous jobid=%u\n", jcr->MigrateJobId);
451    } else {
452       switch (jcr->job->selection_type) {
453       case MT_JOB:
454          if (!regex_find_jobids(jcr, &ji, sql_job, sql_jobids_from_job, "Job")) {
455             goto bail_out;
456          } 
457          break;
458       case MT_CLIENT:
459          if (!regex_find_jobids(jcr, &ji, sql_client, 
460               sql_jobids_from_client, "Client")) {
461             goto bail_out;
462          } 
463          break;
464       case MT_VOLUME:
465          if (!regex_find_jobids(jcr, &ji, sql_vol, 
466              sql_jobids_from_vol, "Volume")) {
467             goto bail_out;
468          } 
469          break;
470       case MT_SQLQUERY:
471          if (!jcr->job->selection_pattern) {
472             Jmsg(jcr, M_FATAL, 0, _("No Migration SQL selection pattern specified.\n"));
473             goto bail_out;
474          }
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));
480             goto bail_out;
481          }
482          break;
483
484
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);
490          break;
491       case MT_OLDEST_VOL:
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);
495          break;
496       case MT_POOL_OCCUPANCY:
497          Mmsg(query, sql_pool_bytes, jcr->pool->hdr.name);
498 //       Dmsg1(000, "Pool Occupancy Jobids=%s\n", JobIds);
499          break;
500       case MT_POOL_TIME:
501          Dmsg0(000, "Pool time not implemented\n");
502          break;
503       default:
504          Jmsg(jcr, M_FATAL, 0, _("Unknown Migration Selection Type.\n"));
505          goto bail_out;
506       }
507    }
508
509    p = ji.JobIds;
510    JobId = 0;
511    stat = get_next_jobid_from_list(&p, &JobId);
512    Dmsg2(000, "get_next_jobid stat=%d JobId=%u\n", stat, JobId);
513    if (stat < 0) {
514       Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
515       goto bail_out;
516    } else if (stat == 0) {
517       Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
518       goto ok_out;
519    }
520    
521    jcr->previous_jr.JobId = JobId;
522    Dmsg1(000, "Last jobid=%d\n", jcr->previous_jr.JobId);
523
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));
528       goto bail_out;
529    }
530    Jmsg(jcr, M_INFO, 0, _("Migration using JobId=%d Job=%s\n"),
531       jcr->previous_jr.JobId, jcr->previous_jr.Job);
532
533 ok_out:
534    free_pool_memory(ji.JobIds);
535    return true;
536
537 bail_out:
538    free_pool_memory(ji.JobIds);
539    return false;
540 }
541
542
543 static bool regex_find_jobids(JCR *jcr, jobitems *ji, const char *query1,
544                  const char *query2, const char *type) {
545    dlist *item_chain;
546    uitem *item = NULL;
547    uitem *last_item = NULL;
548    regex_t preg;
549    char prbuf[500];
550    int rc;
551    bool ok = false;
552    POOL_MEM query(PM_MESSAGE);
553
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"),
557          type);
558       goto bail_out;
559    }
560    Dmsg1(000, "regex=%s\n", jcr->job->selection_pattern);
561    /* Compile regex expression */
562    rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
563    if (rc != 0) {
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);
567       goto bail_out;
568    }
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));
575       goto bail_out;
576    }
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];
581       if (last_item) {
582          Dmsg1(000, "Remove item %s\n", last_item->item);
583          free(last_item->item);
584          item_chain->remove(last_item);
585       }
586       Dmsg1(000, "Jobitem=%s\n", item->item);
587       rc = regexec(&preg, item->item, nmatch, pmatch,  0);
588       if (rc == 0) {
589          last_item = NULL;   /* keep this one */
590       } else {   
591          last_item = item;
592       }
593    }
594    if (last_item) {
595       free(last_item->item);
596       Dmsg1(000, "Remove item %s\n", last_item->item);
597       item_chain->remove(last_item);
598    }
599    regfree(&preg);
600    /* 
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.
604     */
605    ji->count = 0;
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));
612          goto bail_out;
613       }
614    }
615    if (ji->count == 0) {
616       Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
617       ok = true;
618    }
619 bail_out:
620    Dmsg1(000, "Job Jobids=%s\n", ji->JobIds);
621    delete item_chain;
622    return ok;
623 }
624
625
626 /*
627  * Release resources allocated during backup.
628  */
629 void migration_cleanup(JCR *jcr, int TermCode)
630 {
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;
635    int msg_type;
636    MEDIA_DBR mr;
637    double kbps;
638    utime_t RunTime;
639    JCR *prev_jcr = jcr->previous_jcr;
640    POOL_MEM query(PM_MESSAGE);
641
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 */
647
648    /* Check if we actually did something */
649    if (prev_jcr) {
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;
654
655       set_jcr_job_status(prev_jcr, TermCode);
656
657       update_job_end_record(prev_jcr);
658
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);
665
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);
670       }
671
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);
677       }
678
679       update_bootstrap_file(prev_jcr);
680
681       if (!db_get_job_volume_names(prev_jcr, prev_jcr->db, prev_jcr->jr.JobId, &prev_jcr->VolumeName)) {
682          /*
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.
687           */
688          if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
689             Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(prev_jcr->db));
690          }
691          prev_jcr->VolumeName[0] = 0;         /* none */
692       }
693   }
694
695    msg_type = M_INFO;                 /* by default INFO message */
696    switch (jcr->JobStatus) {
697       case JS_Terminated:
698          if (jcr->Errors || jcr->SDErrors) {
699             term_msg = _("%s OK -- with warnings");
700          } else {
701             term_msg = _("%s OK");
702          }
703          break;
704       case JS_FatalError:
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);
712             }
713          }
714          break;
715       case JS_Canceled:
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);
721             }
722          }
723          break;
724       default:
725          term_msg = _("Inappropriate %s term code");
726          break;
727    }
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;
732    if (RunTime <= 0) {
733       kbps = 0;
734    } else {
735       kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
736    }
737
738
739    jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
740
741 // bmicrosleep(15, 0);                /* for debugging SIGHUP */
742
743    Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
744 "  Old Backup JobId:       %u\n"
745 "  New Backup JobId:       %u\n"
746 "  JobId:                  %u\n"
747 "  Job:                    %s\n"
748 "  Backup Level:           %s%s\n"
749 "  Client:                 %s\n"
750 "  FileSet:                \"%s\" %s\n"
751 "  Pool:                   \"%s\"\n"
752 "  Start time:             %s\n"
753 "  End time:               %s\n"
754 "  Elapsed time:           %s\n"
755 "  Priority:               %d\n"
756 "  SD Files Written:       %s\n"
757 "  SD Bytes Written:       %s (%sB)\n"
758 "  Rate:                   %.1f KB/s\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"
763 "  SD Errors:              %d\n"
764 "  SD termination status:  %s\n"
765 "  Termination:            %s\n\n"),
766    VERSION,
767    LSMDATE,
768         edt, 
769         prev_jcr ? jcr->previous_jr.JobId : 0, 
770         prev_jcr ? prev_jcr->jr.JobId : 0,
771         jcr->jr.JobId,
772         jcr->jr.Job,
773         level_to_str(jcr->JobLevel), jcr->since,
774         jcr->client->hdr.name,
775         jcr->fileset->hdr.name, jcr->FSCreateTime,
776         jcr->pool->hdr.name,
777         sdt,
778         edt,
779         edit_utime(RunTime, elapsed, sizeof(elapsed)),
780         jcr->JobPriority,
781         edit_uint64_with_commas(jcr->SDJobFiles, ec1),
782         edit_uint64_with_commas(jcr->SDJobBytes, ec2),
783         edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
784         (float)kbps,
785         prev_jcr ? prev_jcr->VolumeName : "",
786         jcr->VolSessionId,
787         jcr->VolSessionTime,
788         edit_uint64_with_commas(mr.VolBytes, ec4),
789         edit_uint64_with_suffix(mr.VolBytes, ec5),
790         jcr->SDErrors,
791         sd_term_msg,
792         term_code);
793
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;
798    }
799    Dmsg0(000, "Leave migrate_cleanup()\n");
800 }