]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/migrate.c
Make migration work with new subroutine
[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 idpkt;
44 static bool regex_find_jobids(JCR *jcr, idpkt *ids, 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 idpkt {
302    POOLMEM *list;
303    uint32_t count;
304 };
305
306 /*
307  * Callback handler make list of DB Ids
308  */
309 static int dbid_handler(void *ctx, int num_fields, char **row)
310 {
311    idpkt *ids = (idpkt *)ctx;
312
313    Dmsg3(000, "count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
314    if (ids->count == 0) {
315       ids->list[0] = 0;
316    } else {
317       pm_strcat(ids->list, ",");
318    }
319    pm_strcat(ids->list, row[0]);
320    ids->count++;
321    return 0;
322 }
323
324
325 struct uitem {
326    dlink link;   
327    char *item;
328 };
329
330 static int item_compare(void *item1, void *item2)
331 {
332    uitem *i1 = (uitem *)item1;
333    uitem *i2 = (uitem *)item2;
334    return strcmp(i1->item, i2->item);
335 }
336
337 static int unique_name_handler(void *ctx, int num_fields, char **row)
338 {
339    dlist *list = (dlist *)ctx;
340
341    uitem *new_item = (uitem *)malloc(sizeof(uitem));
342    uitem *item;
343    
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);
351       return 0;
352    }
353    return 0;
354 }
355
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";
360
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";
366
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";
372
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";
379
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'";
385
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";
392    
393
394
395
396
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";
402
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";
408
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";
413
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'";
418
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";
424
425
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";
430
431
432
433 /*
434  * Returns: false on error
435  *          true  if OK and jcr->previous_jr filled in
436  */
437 static bool get_job_to_migrate(JCR *jcr)
438 {
439    char ed1[30];
440    POOL_MEM query(PM_MESSAGE);
441    JobId_t JobId;
442    int stat;
443    char *p;
444    idpkt ids;
445
446    ids.list = get_pool_memory(PM_MESSAGE);
447    Dmsg1(000, "list=%p\n", ids.list);
448    ids.list[0] = 0;
449    ids.count = 0;
450
451    if (jcr->MigrateJobId != 0) {
452       jcr->previous_jr.JobId = jcr->MigrateJobId;
453       Dmsg1(000, "previous jobid=%u\n", jcr->MigrateJobId);
454    } else {
455       switch (jcr->job->selection_type) {
456       case MT_JOB:
457          if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
458             goto bail_out;
459          } 
460          break;
461       case MT_CLIENT:
462          if (!regex_find_jobids(jcr, &ids, sql_client, 
463               sql_jobids_from_client, "Client")) {
464             goto bail_out;
465          } 
466          break;
467       case MT_VOLUME:
468          if (!regex_find_jobids(jcr, &ids, sql_vol, 
469              sql_jobids_from_vol, "Volume")) {
470             goto bail_out;
471          } 
472          break;
473       case MT_SQLQUERY:
474          if (!jcr->job->selection_pattern) {
475             Jmsg(jcr, M_FATAL, 0, _("No Migration SQL selection pattern specified.\n"));
476             goto bail_out;
477          }
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));
483             goto bail_out;
484          }
485          break;
486
487
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);
493          break;
494       case MT_OLDEST_VOL:
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);
498          break;
499       case MT_POOL_OCCUPANCY:
500          Mmsg(query, sql_pool_bytes, jcr->pool->hdr.name);
501 //       Dmsg1(000, "Pool Occupancy Jobids=%s\n", JobIds);
502          break;
503       case MT_POOL_TIME:
504          Dmsg0(000, "Pool time not implemented\n");
505          break;
506       default:
507          Jmsg(jcr, M_FATAL, 0, _("Unknown Migration Selection Type.\n"));
508          goto bail_out;
509       }
510    }
511
512    p = ids.list;
513    JobId = 0;
514    stat = get_next_jobid_from_list(&p, &JobId);
515    Dmsg2(000, "get_next_jobid stat=%d JobId=%u\n", stat, JobId);
516    if (stat < 0) {
517       Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
518       goto bail_out;
519    } else if (stat == 0) {
520       Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
521       goto ok_out;
522    }
523    
524    jcr->previous_jr.JobId = JobId;
525    Dmsg1(000, "Last jobid=%d\n", jcr->previous_jr.JobId);
526
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));
531       goto bail_out;
532    }
533    Jmsg(jcr, M_INFO, 0, _("Migration using JobId=%d Job=%s\n"),
534       jcr->previous_jr.JobId, jcr->previous_jr.Job);
535
536 ok_out:
537    free_pool_memory(ids.list);
538    return true;
539
540 bail_out:
541    free_pool_memory(ids.list);
542    return false;
543 }
544
545
546 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
547                  const char *query2, const char *type) {
548    dlist *item_chain;
549    uitem *item = NULL;
550    uitem *last_item = NULL;
551    regex_t preg;
552    char prbuf[500];
553    int rc;
554    bool ok = false;
555    POOL_MEM query(PM_MESSAGE);
556
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"),
560          type);
561       goto bail_out;
562    }
563    Dmsg1(000, "regex=%s\n", jcr->job->selection_pattern);
564    /* Compile regex expression */
565    rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
566    if (rc != 0) {
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);
570       goto bail_out;
571    }
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));
579       goto bail_out;
580    }
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];
585       if (last_item) {
586          Dmsg1(000, "Remove item %s\n", last_item->item);
587          free(last_item->item);
588          item_chain->remove(last_item);
589       }
590       Dmsg1(000, "Jobitem=%s\n", item->item);
591       rc = regexec(&preg, item->item, nmatch, pmatch,  0);
592       if (rc == 0) {
593          last_item = NULL;   /* keep this one */
594       } else {   
595          last_item = item;
596       }
597    }
598    if (last_item) {
599       free(last_item->item);
600       Dmsg1(000, "Remove item %s\n", last_item->item);
601       item_chain->remove(last_item);
602    }
603    regfree(&preg);
604    /* 
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.
608     */
609    ids->count = 0;
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));
617          goto bail_out;
618       }
619    }
620    if (ids->count == 0) {
621       Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
622    }
623    ok = true;
624 bail_out:
625    Dmsg2(000, "Count=%d Jobids=%s\n", ids->count, ids->list);
626    delete item_chain;
627    Dmsg0(000, "After delete item_chain\n");
628    return ok;
629 }
630
631
632 /*
633  * Release resources allocated during backup.
634  */
635 void migration_cleanup(JCR *jcr, int TermCode)
636 {
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;
642    int msg_type;
643    MEDIA_DBR mr;
644    double kbps;
645    utime_t RunTime;
646    JCR *prev_jcr = jcr->previous_jcr;
647    POOL_MEM query(PM_MESSAGE);
648
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 */
654
655    /* Check if we actually did something */
656    if (prev_jcr) {
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;
661
662       set_jcr_job_status(prev_jcr, TermCode);
663
664       update_job_end_record(prev_jcr);
665
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);
672
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);
677       }
678
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);
684       }
685
686       update_bootstrap_file(prev_jcr);
687
688       if (!db_get_job_volume_names(prev_jcr, prev_jcr->db, prev_jcr->jr.JobId, &prev_jcr->VolumeName)) {
689          /*
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.
694           */
695          if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
696             Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(prev_jcr->db));
697          }
698          prev_jcr->VolumeName[0] = 0;         /* none */
699       }
700   }
701
702    msg_type = M_INFO;                 /* by default INFO message */
703    switch (jcr->JobStatus) {
704       case JS_Terminated:
705          if (jcr->Errors || jcr->SDErrors) {
706             term_msg = _("%s OK -- with warnings");
707          } else {
708             term_msg = _("%s OK");
709          }
710          break;
711       case JS_FatalError:
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);
719             }
720          }
721          break;
722       case JS_Canceled:
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);
728             }
729          }
730          break;
731       default:
732          term_msg = _("Inappropriate %s term code");
733          break;
734    }
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;
739    if (RunTime <= 0) {
740       kbps = 0;
741    } else {
742       kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
743    }
744
745
746    jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
747
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"
754 "  Client:                 %s\n"
755 "  FileSet:                \"%s\" %s\n"
756 "  Pool:                   \"%s\"\n"
757 "  Start time:             %s\n"
758 "  End time:               %s\n"
759 "  Elapsed time:           %s\n"
760 "  Priority:               %d\n"
761 "  SD Files Written:       %s\n"
762 "  SD Bytes Written:       %s (%sB)\n"
763 "  Rate:                   %.1f KB/s\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"
768 "  SD Errors:              %d\n"
769 "  SD termination status:  %s\n"
770 "  Termination:            %s\n\n"),
771    VERSION,
772    LSMDATE,
773         edt, 
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),
777         jcr->jr.Job,
778         level_to_str(jcr->JobLevel), jcr->since,
779         jcr->client->hdr.name,
780         jcr->fileset->hdr.name, jcr->FSCreateTime,
781         jcr->pool->hdr.name,
782         sdt,
783         edt,
784         edit_utime(RunTime, elapsed, sizeof(elapsed)),
785         jcr->JobPriority,
786         edit_uint64_with_commas(jcr->SDJobFiles, ec1),
787         edit_uint64_with_commas(jcr->SDJobBytes, ec2),
788         edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
789         (float)kbps,
790         prev_jcr ? prev_jcr->VolumeName : "",
791         jcr->VolSessionId,
792         jcr->VolSessionTime,
793         edit_uint64_with_commas(mr.VolBytes, ec4),
794         edit_uint64_with_suffix(mr.VolBytes, ec5),
795         jcr->SDErrors,
796         sd_term_msg,
797         term_code);
798
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;
803    }
804    Dmsg0(100, "Leave migrate_cleanup()\n");
805 }