]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/backup.c
Don't compute accurate list with MD5 if not used
[bacula/bacula] / bacula / src / dird / backup.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2009 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *
30  *   Bacula Director -- backup.c -- responsible for doing backup jobs
31  *
32  *     Kern Sibbald, March MM
33  *
34  *  Basic tasks done here:
35  *     Open DB and create records for this job.
36  *     Open Message Channel with Storage daemon to tell him a job will be starting.
37  *     Open connection with File daemon and pass him commands
38  *       to do the backup.
39  *     When the File daemon finishes the job, update the DB.
40  *
41  *   Version $Id$
42  */
43
44 #include "bacula.h"
45 #include "dird.h"
46 #include "ua.h"
47
48 /* Commands sent to File daemon */
49 static char backupcmd[] = "backup\n";
50 static char storaddr[]  = "storage address=%s port=%d ssl=%d\n";
51
52 /* Responses received from File daemon */
53 static char OKbackup[]   = "2000 OK backup\n";
54 static char OKstore[]    = "2000 OK storage\n";
55 static char EndJob[]     = "2800 End Job TermCode=%d JobFiles=%u "
56                            "ReadBytes=%llu JobBytes=%llu Errors=%u "  
57                            "VSS=%d Encrypt=%d\n";
58 /* Pre 1.39.29 (04Dec06) EndJob */
59 static char OldEndJob[]  = "2800 End Job TermCode=%d JobFiles=%u "
60                            "ReadBytes=%llu JobBytes=%llu Errors=%u\n";
61 /* 
62  * Called here before the job is run to do the job
63  *   specific setup.
64  */
65 bool do_backup_init(JCR *jcr)
66 {
67
68    if (jcr->getJobLevel() == L_VIRTUAL_FULL) {
69       return do_vbackup_init(jcr);
70    }
71    free_rstorage(jcr);                   /* we don't read so release */
72
73    if (!get_or_create_fileset_record(jcr)) {
74       return false;
75    }
76
77    /* 
78     * Get definitive Job level and since time
79     */
80    get_level_since_time(jcr, jcr->since, sizeof(jcr->since));
81
82    apply_pool_overrides(jcr);
83
84    if (!allow_duplicate_job(jcr)) {
85       return false;
86    }
87
88    jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->name());
89    if (jcr->jr.PoolId == 0) {
90       return false;
91    }
92
93    /* If pool storage specified, use it instead of job storage */
94    copy_wstorage(jcr, jcr->pool->storage, _("Pool resource"));
95
96    if (!jcr->wstorage) {
97       Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Job or Pool.\n"));
98       return false;
99    }
100
101    create_clones(jcr);                /* run any clone jobs */
102
103    return true;
104 }
105
106 /* Take all base jobs from job resource and find the
107  * last L_BASE jobid.
108  */
109 static bool get_base_jobids(JCR *jcr, db_list_ctx *jobids)
110 {
111    JOB_DBR jr;
112    JOB *job;
113    JobId_t id;
114    char str_jobid[50];
115
116    if (!jcr->job->base) {
117       return false;             /* no base job, stop accurate */
118    }
119
120    memset(&jr, 0, sizeof(JOB_DBR));
121    jr.StartTime = jcr->jr.StartTime;
122
123    foreach_alist(job, jcr->job->base) {
124       bstrncpy(jr.Name, job->name(), sizeof(jr.Name));
125       db_get_base_jobid(jcr, jcr->db, &jr, &id);
126
127       if (id) {
128          if (jobids->count) {
129             pm_strcat(jobids->list, ",");
130          }
131          pm_strcat(jobids->list, edit_uint64(id, str_jobid));
132          jobids->count++;
133       }
134    }
135
136    return jobids->count > 0;
137 }
138
139 /*
140  * Foreach files in currrent list, send "/path/fname\0LStat\0MD5\0Delta" to FD
141  *      row[0]=Path, row[1]=Filename, row[2]=FileIndex
142  *      row[3]=JobId row[4]=LStat row[5]=MarkId row[6]=MD5
143  */
144 static int accurate_list_handler(void *ctx, int num_fields, char **row)
145 {
146    JCR *jcr = (JCR *)ctx;
147
148    if (job_canceled(jcr)) {
149       return 1;
150    }
151    
152    if (row[2][0] == '0') {           /* discard when file_index == 0 */
153       return 0;
154    }
155
156    /* sending with checksum */
157    if (jcr->use_accurate_chksum 
158        && num_fields == 7 
159        && row[6][0] /* skip checksum = '0' */
160        && row[6][1])
161    { 
162       jcr->file_bsock->fsend("%s%s%c%s%c%s%c%s", 
163                              row[0], row[1], 0, row[4], 0, row[6], 0, row[5]); 
164    } else {
165       jcr->file_bsock->fsend("%s%s%c%s%c%c%s", 
166                              row[0], row[1], 0, row[4], 0, 0, row[5]); 
167    }
168    return 0;
169 }
170
171 /* In this procedure, we check if the current fileset is using checksum
172  * FileSet-> Include-> Options-> Accurate/Verify/BaseJob=checksum
173  * This procedure uses jcr->HasBase, so it must be call after the initialization
174  */
175 static bool is_checksum_needed_by_fileset(JCR *jcr)
176 {
177    FILESET *f;
178    INCEXE *inc;
179    FOPTS *fopts;
180    bool in_block=false;
181    bool have_basejob_option=false;
182    if (!jcr->job || !jcr->job->fileset) {
183       return false;
184    }
185
186    f = jcr->job->fileset;
187    
188    for (int i=0; i < f->num_includes; i++) { /* Parse all Include {} */
189       inc = f->include_items[i];
190       
191       for (int j=0; j < inc->num_opts; j++) { /* Parse all Options {} */
192          fopts = inc->opts_list[j];
193          
194          for (char *k=fopts->opts; *k ; k++) { /* Try to find one request */
195             switch (*k) {
196             case 'V':           /* verify */
197                in_block = (jcr->getJobType() == JT_VERIFY); /* not used now */
198                break;
199             case 'J':           /* Basejob keyword */
200                have_basejob_option = in_block = jcr->HasBase;
201                break;
202             case 'C':           /* Accurate keyword */
203                in_block = (jcr->getJobLevel() != L_FULL);
204                break;
205             case ':':           /* End of keyword */
206                in_block = false;
207                break;
208             case '5':           /* MD5  */
209             case '1':           /* SHA1 */
210                if (in_block) {
211                   Dmsg0(50, "Checksum will be sent to FD\n");
212                   return true;
213                }
214                break;
215             default:
216                break;
217             }
218          }
219       }
220    }
221
222    /* By default for BaseJobs, we send the checksum */
223    if (!have_basejob_option && jcr->HasBase) {
224       return true;
225    }
226    
227    Dmsg0(50, "Checksum will be sent to FD\n");
228    return false;
229 }
230
231 /*
232  * Send current file list to FD
233  *    DIR -> FD : accurate files=xxxx
234  *    DIR -> FD : /path/to/file\0Lstat\0MD5\0Delta
235  *    DIR -> FD : /path/to/dir/\0Lstat\0MD5\0Delta
236  *    ...
237  *    DIR -> FD : EOD
238  */
239 bool send_accurate_current_files(JCR *jcr)
240 {
241    POOL_MEM buf;
242    bool ret=true;
243    db_list_ctx jobids;
244    db_list_ctx nb;
245
246    if (!jcr->accurate || job_canceled(jcr)) {
247       return true;
248    }
249    /* In base level, no previous job is used */
250    if (jcr->getJobLevel() == L_BASE) {
251       return true;
252    }
253    
254    if (jcr->getJobLevel() == L_FULL) {
255       /* On Full mode, if no previous base job, no accurate things */
256       if (!get_base_jobids(jcr, &jobids)) {
257          goto bail_out;
258       }
259       jcr->HasBase = true;
260       Jmsg(jcr, M_INFO, 0, _("Using BaseJobId(s): %s\n"), jobids.list);
261
262    } else {
263       /* For Incr/Diff level, we search for older jobs */
264       db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, &jobids);
265
266       /* We are in Incr/Diff, but no Full to build the accurate list... */
267       if (jobids.count == 0) {
268          ret=false;
269          Jmsg(jcr, M_FATAL, 0, _("Cannot find previous jobids.\n"));
270          goto bail_out;
271       }
272    }
273
274    /* Don't send and store the checksum if fileset doesn't require it */
275    jcr->use_accurate_chksum = is_checksum_needed_by_fileset(jcr);
276
277    if (jcr->JobId) {            /* display the message only for real jobs */
278       Jmsg(jcr, M_INFO, 0, _("Sending Accurate information.\n"));
279    }
280
281    /* to be able to allocate the right size for htable */
282    Mmsg(buf, "SELECT sum(JobFiles) FROM Job WHERE JobId IN (%s)", jobids.list);
283    db_sql_query(jcr->db, buf.c_str(), db_list_handler, &nb);
284    Dmsg2(200, "jobids=%s nb=%s\n", jobids.list, nb.list);
285    jcr->file_bsock->fsend("accurate files=%s\n", nb.list); 
286
287    if (!db_open_batch_connexion(jcr, jcr->db)) {
288       Jmsg0(jcr, M_FATAL, 0, "Can't get batch sql connexion");
289       return false;
290    }
291    
292    if (jcr->HasBase) {
293       jcr->nb_base_files = str_to_int64(nb.list);
294       db_create_base_file_list(jcr, jcr->db, jobids.list);
295       db_get_base_file_list(jcr, jcr->db, jcr->use_accurate_chksum,
296                             accurate_list_handler, (void *)jcr);
297
298    } else {
299       db_get_file_list(jcr, jcr->db_batch,
300                        jobids.list, jcr->use_accurate_chksum,
301                        accurate_list_handler, (void *)jcr);
302    } 
303
304    /* TODO: close the batch connexion ? (can be used very soon) */
305
306    jcr->file_bsock->signal(BNET_EOD);
307
308 bail_out:
309    return ret;
310 }
311
312 /*
313  * Do a backup of the specified FileSet
314  *
315  *  Returns:  false on failure
316  *            true  on success
317  */
318 bool do_backup(JCR *jcr)
319 {
320    int stat;
321    int tls_need = BNET_TLS_NONE;
322    BSOCK   *fd;
323    STORE *store;
324    char ed1[100];
325
326    if (jcr->getJobLevel() == L_VIRTUAL_FULL) {
327       return do_vbackup(jcr);
328    }
329
330    /* Print Job Start message */
331    Jmsg(jcr, M_INFO, 0, _("Start Backup JobId %s, Job=%s\n"),
332         edit_uint64(jcr->JobId, ed1), jcr->Job);
333
334    jcr->setJobStatus(JS_Running);
335    Dmsg2(100, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
336    if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
337       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
338       return false;
339    }
340
341    /*
342     * Open a message channel connection with the Storage
343     * daemon. This is to let him know that our client
344     * will be contacting him for a backup  session.
345     *
346     */
347    Dmsg0(110, "Open connection with storage daemon\n");
348    jcr->setJobStatus(JS_WaitSD);
349    /*
350     * Start conversation with Storage daemon
351     */
352    if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
353       return false;
354    }
355    /*
356     * Now start a job with the Storage daemon
357     */
358    if (!start_storage_daemon_job(jcr, NULL, jcr->wstorage)) {
359       return false;
360    }
361
362    /*
363     * Start the job prior to starting the message thread below
364     * to avoid two threads from using the BSOCK structure at
365     * the same time.
366     */
367    if (!bnet_fsend(jcr->store_bsock, "run")) {
368       return false;
369    }
370
371    /*
372     * Now start a Storage daemon message thread.  Note,
373     *   this thread is used to provide the catalog services
374     *   for the backup job, including inserting the attributes
375     *   into the catalog.  See catalog_update() in catreq.c
376     */
377    if (!start_storage_daemon_message_thread(jcr)) {
378       return false;
379    }
380    Dmsg0(150, "Storage daemon connection OK\n");
381
382    jcr->setJobStatus(JS_WaitFD);
383    if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
384       goto bail_out;
385    }
386
387    jcr->setJobStatus(JS_Running);
388    fd = jcr->file_bsock;
389
390    if (!send_include_list(jcr)) {
391       goto bail_out;
392    }
393
394    if (!send_exclude_list(jcr)) {
395       goto bail_out;
396    }
397
398    if (!send_level_command(jcr)) {
399       goto bail_out;
400    }
401
402    /*
403     * send Storage daemon address to the File daemon
404     */
405    store = jcr->wstore;
406    if (store->SDDport == 0) {
407       store->SDDport = store->SDport;
408    }
409
410    /* TLS Requirement */
411    if (store->tls_enable) {
412       if (store->tls_require) {
413          tls_need = BNET_TLS_REQUIRED;
414       } else {
415          tls_need = BNET_TLS_OK;
416       }
417    }
418
419    fd->fsend(storaddr, store->address, store->SDDport, tls_need);
420    if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
421       goto bail_out;
422    }
423
424    if (!send_runscripts_commands(jcr)) {
425       goto bail_out;
426    }
427
428    /*    
429     * We re-update the job start record so that the start
430     *  time is set after the run before job.  This avoids 
431     *  that any files created by the run before job will
432     *  be saved twice.  They will be backed up in the current
433     *  job, but not in the next one unless they are changed.
434     *  Without this, they will be backed up in this job and
435     *  in the next job run because in that case, their date 
436     *   is after the start of this run.
437     */
438    jcr->start_time = time(NULL);
439    jcr->jr.StartTime = jcr->start_time;
440    if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
441       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
442    }
443
444    /*
445     * If backup is in accurate mode, we send the list of
446     * all files to FD.
447     */
448    if (!send_accurate_current_files(jcr)) {
449       goto bail_out;
450    }
451
452    /* Send backup command */
453    fd->fsend(backupcmd);
454    if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) {
455       goto bail_out;
456    }
457
458    /* Pickup Job termination data */
459    stat = wait_for_job_termination(jcr);
460    db_write_batch_file_records(jcr);    /* used by bulk batch file insert */
461
462    if (jcr->HasBase && 
463        !db_commit_base_file_attributes_record(jcr, jcr->db)) 
464    {
465          Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
466    }
467
468    if (stat == JS_Terminated) {
469       backup_cleanup(jcr, stat);
470       return true;
471    }     
472    return false;
473
474 /* Come here only after starting SD thread */
475 bail_out:
476    jcr->setJobStatus(JS_ErrorTerminated);
477    Dmsg1(400, "wait for sd. use=%d\n", jcr->use_count());
478    /* Cancel SD */
479    wait_for_job_termination(jcr, FDConnectTimeout);
480    Dmsg1(400, "after wait for sd. use=%d\n", jcr->use_count());
481    return false;
482 }
483
484
485 /*
486  * Here we wait for the File daemon to signal termination,
487  *   then we wait for the Storage daemon.  When both
488  *   are done, we return the job status.
489  * Also used by restore.c
490  */
491 int wait_for_job_termination(JCR *jcr, int timeout)
492 {
493    int32_t n = 0;
494    BSOCK *fd = jcr->file_bsock;
495    bool fd_ok = false;
496    uint32_t JobFiles, JobErrors;
497    uint32_t JobWarnings = 0;
498    uint64_t ReadBytes = 0;
499    uint64_t JobBytes = 0;
500    int VSS = 0;
501    int Encrypt = 0;
502    btimer_t *tid=NULL;
503
504    jcr->setJobStatus(JS_Running);
505
506    if (fd) {
507       if (timeout) {
508          tid = start_bsock_timer(fd, timeout); /* TODO: New timeout directive??? */
509       }
510       /* Wait for Client to terminate */
511       while ((n = bget_dirmsg(fd)) >= 0) {
512          if (!fd_ok && 
513              (sscanf(fd->msg, EndJob, &jcr->FDJobStatus, &JobFiles,
514                      &ReadBytes, &JobBytes, &JobErrors, &VSS, &Encrypt) == 7 ||
515               sscanf(fd->msg, OldEndJob, &jcr->FDJobStatus, &JobFiles,
516                      &ReadBytes, &JobBytes, &JobErrors) == 5)) {
517             fd_ok = true;
518             jcr->setJobStatus(jcr->FDJobStatus);
519             Dmsg1(100, "FDStatus=%c\n", (char)jcr->JobStatus);
520          } else {
521             Jmsg(jcr, M_WARNING, 0, _("Unexpected Client Job message: %s\n"),
522                  fd->msg);
523          }
524          if (job_canceled(jcr)) {
525             break;
526          }
527       }
528       if (tid) {
529          stop_bsock_timer(tid);
530       }
531
532       if (is_bnet_error(fd)) {
533          Jmsg(jcr, M_FATAL, 0, _("Network error with FD during %s: ERR=%s\n"),
534               job_type_to_str(jcr->getJobType()), fd->bstrerror());
535       }
536       fd->signal(BNET_TERMINATE);   /* tell Client we are terminating */
537    }
538
539    /* Force cancel in SD if failing */
540    if (job_canceled(jcr) || !fd_ok) {
541       cancel_storage_daemon_job(jcr);
542    }
543
544    /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
545    wait_for_storage_daemon_termination(jcr);
546
547    /* Return values from FD */
548    if (fd_ok) {
549       jcr->JobFiles = JobFiles;
550       jcr->JobErrors += JobErrors;       /* Keep total errors */
551       jcr->ReadBytes = ReadBytes;
552       jcr->JobBytes = JobBytes;
553       jcr->JobWarnings = JobWarnings;
554       jcr->VSS = VSS;
555       jcr->Encrypt = Encrypt;
556    } else {
557       Jmsg(jcr, M_FATAL, 0, _("No Job status returned from FD.\n"));
558    }
559
560 // Dmsg4(100, "fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", fd_ok, jcr->FDJobStatus,
561 //   jcr->JobStatus, jcr->SDJobStatus);
562
563    /* Return the first error status we find Dir, FD, or SD */
564    if (!fd_ok || is_bnet_error(fd)) { /* if fd not set, that use !fd_ok */
565       jcr->FDJobStatus = JS_ErrorTerminated;
566    }
567    if (jcr->JobStatus != JS_Terminated) {
568       return jcr->JobStatus;
569    }
570    if (jcr->FDJobStatus != JS_Terminated) {
571       return jcr->FDJobStatus;
572    }
573    return jcr->SDJobStatus;
574 }
575
576 /*
577  * Release resources allocated during backup.
578  */
579 void backup_cleanup(JCR *jcr, int TermCode)
580 {
581    char sdt[50], edt[50], schedt[50];
582    char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], compress[50];
583    char ec6[30], ec7[30], ec8[30], elapsed[50];
584    char term_code[100], fd_term_msg[100], sd_term_msg[100];
585    const char *term_msg;
586    int msg_type = M_INFO;
587    MEDIA_DBR mr;
588    CLIENT_DBR cr;
589    double kbps, compression;
590    utime_t RunTime;
591    POOL_MEM base_info;
592
593    if (jcr->getJobLevel() == L_VIRTUAL_FULL) {
594       vbackup_cleanup(jcr, TermCode);
595       return;
596    }
597
598    Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
599    memset(&mr, 0, sizeof(mr));
600    memset(&cr, 0, sizeof(cr));
601
602 #ifdef xxxx
603    if (jcr->getJobStatus() == JS_Terminated && 
604         (jcr->JobErrors || jcr->SDErrors || jcr->JobWarnings)) {
605       TermCode = JS_Warnings;
606    }
607 #endif
608          
609    update_job_end(jcr, TermCode);
610
611    if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
612       Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
613          db_strerror(jcr->db));
614       jcr->setJobStatus(JS_ErrorTerminated);
615    }
616
617    bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
618    if (!db_get_client_record(jcr, jcr->db, &cr)) {
619       Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
620          db_strerror(jcr->db));
621    }
622
623    bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
624    if (!db_get_media_record(jcr, jcr->db, &mr)) {
625       Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
626          mr.VolumeName, db_strerror(jcr->db));
627       jcr->setJobStatus(JS_ErrorTerminated);
628    }
629
630    update_bootstrap_file(jcr);
631
632    switch (jcr->JobStatus) {
633       case JS_Terminated:
634          if (jcr->JobErrors || jcr->SDErrors) {
635             term_msg = _("Backup OK -- with warnings");
636          } else {
637             term_msg = _("Backup OK");
638          }
639          break;
640       case JS_Warnings:
641          term_msg = _("Backup OK -- with warnings");
642          break;
643       case JS_FatalError:
644       case JS_ErrorTerminated:
645          term_msg = _("*** Backup Error ***");
646          msg_type = M_ERROR;          /* Generate error message */
647          if (jcr->store_bsock) {
648             jcr->store_bsock->signal(BNET_TERMINATE);
649             if (jcr->SD_msg_chan) {
650                pthread_cancel(jcr->SD_msg_chan);
651             }
652          }
653          break;
654       case JS_Canceled:
655          term_msg = _("Backup Canceled");
656          if (jcr->store_bsock) {
657             jcr->store_bsock->signal(BNET_TERMINATE);
658             if (jcr->SD_msg_chan) {
659                pthread_cancel(jcr->SD_msg_chan);
660             }
661          }
662          break;
663       default:
664          term_msg = term_code;
665          sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
666          break;
667    }
668    bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
669    bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
670    bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
671    RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
672    if (RunTime <= 0) {
673       kbps = 0;
674    } else {
675       kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
676    }
677    if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
678       /*
679        * Note, if the job has erred, most likely it did not write any
680        *  tape, so suppress this "error" message since in that case
681        *  it is normal.  Or look at it the other way, only for a
682        *  normal exit should we complain about this error.
683        */
684       if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
685          Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
686       }
687       jcr->VolumeName[0] = 0;         /* none */
688    }
689
690    if (jcr->ReadBytes == 0) {
691       bstrncpy(compress, "None", sizeof(compress));
692    } else {
693       compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
694       if (compression < 0.5) {
695          bstrncpy(compress, "None", sizeof(compress));
696       } else {
697          bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
698       }
699    }
700    jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
701    jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
702
703    if (jcr->HasBase) {
704       Mmsg(base_info, "  Base files/Used files:  %lld/%lld (%.2f%%)\n",
705            jcr->nb_base_files, 
706            jcr->nb_base_files_used, 
707            jcr->nb_base_files_used*100.0/jcr->nb_base_files);
708    }
709 // bmicrosleep(15, 0);                /* for debugging SIGHUP */
710
711    Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
712 "  Build OS:               %s %s %s\n"
713 "  JobId:                  %d\n"
714 "  Job:                    %s\n"
715 "  Backup Level:           %s%s\n"
716 "  Client:                 \"%s\" %s\n"
717 "  FileSet:                \"%s\" %s\n"
718 "  Pool:                   \"%s\" (From %s)\n"
719 "  Catalog:                \"%s\" (From %s)\n"
720 "  Storage:                \"%s\" (From %s)\n"
721 "  Scheduled time:         %s\n"
722 "  Start time:             %s\n"
723 "  End time:               %s\n"
724 "  Elapsed time:           %s\n"
725 "  Priority:               %d\n"
726 "  FD Files Written:       %s\n"
727 "  SD Files Written:       %s\n"
728 "  FD Bytes Written:       %s (%sB)\n"
729 "  SD Bytes Written:       %s (%sB)\n"
730 "  Rate:                   %.1f KB/s\n"
731 "  Software Compression:   %s\n"
732 "%s"                                         /* Basefile info */
733 "  VSS:                    %s\n"
734 "  Encryption:             %s\n"
735 "  Accurate:               %s\n"
736 "  Volume name(s):         %s\n"
737 "  Volume Session Id:      %d\n"
738 "  Volume Session Time:    %d\n"
739 "  Last Volume Bytes:      %s (%sB)\n"
740 "  Non-fatal FD errors:    %d\n"
741 "  SD Errors:              %d\n"
742 "  FD termination status:  %s\n"
743 "  SD termination status:  %s\n"
744 "  Termination:            %s\n\n"),
745         BACULA, my_name, VERSION, LSMDATE,
746         HOST_OS, DISTNAME, DISTVER,
747         jcr->jr.JobId,
748         jcr->jr.Job,
749         level_to_str(jcr->getJobLevel()), jcr->since,
750         jcr->client->name(), cr.Uname,
751         jcr->fileset->name(), jcr->FSCreateTime,
752         jcr->pool->name(), jcr->pool_source,
753         jcr->catalog->name(), jcr->catalog_source,
754         jcr->wstore->name(), jcr->wstore_source,
755         schedt,
756         sdt,
757         edt,
758         edit_utime(RunTime, elapsed, sizeof(elapsed)),
759         jcr->JobPriority,
760         edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
761         edit_uint64_with_commas(jcr->SDJobFiles, ec2),
762         edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
763         edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
764         edit_uint64_with_commas(jcr->SDJobBytes, ec5),
765         edit_uint64_with_suffix(jcr->SDJobBytes, ec6),
766         kbps,
767         compress,
768         base_info.c_str(),
769         jcr->VSS?_("yes"):_("no"),
770         jcr->Encrypt?_("yes"):_("no"),
771         jcr->accurate?_("yes"):_("no"),
772         jcr->VolumeName,
773         jcr->VolSessionId,
774         jcr->VolSessionTime,
775         edit_uint64_with_commas(mr.VolBytes, ec7),
776         edit_uint64_with_suffix(mr.VolBytes, ec8),
777         jcr->JobErrors,
778         jcr->SDErrors,
779         fd_term_msg,
780         sd_term_msg,
781         term_msg);
782
783    Dmsg0(100, "Leave backup_cleanup()\n");
784 }
785
786 void update_bootstrap_file(JCR *jcr)
787 {
788    /* Now update the bootstrap file if any */
789    if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes &&
790        jcr->job->WriteBootstrap) {
791       FILE *fd;
792       BPIPE *bpipe = NULL;
793       int got_pipe = 0;
794       POOLMEM *fname = get_pool_memory(PM_FNAME);
795       fname = edit_job_codes(jcr, fname, jcr->job->WriteBootstrap, "");
796
797       VOL_PARAMS *VolParams = NULL;
798       int VolCount;
799       char edt[50], ed1[50], ed2[50];
800
801       if (*fname == '|') {
802          got_pipe = 1;
803          bpipe = open_bpipe(fname+1, 0, "w"); /* skip first char "|" */
804          fd = bpipe ? bpipe->wfd : NULL;
805       } else {
806          /* ***FIXME*** handle BASE */
807          fd = fopen(fname, jcr->getJobLevel()==L_FULL?"w+b":"a+b");
808       }
809       if (fd) {
810          VolCount = db_get_job_volume_parameters(jcr, jcr->db, jcr->JobId,
811                     &VolParams);
812          if (VolCount == 0) {
813             Jmsg(jcr, M_ERROR, 0, _("Could not get Job Volume Parameters to "
814                  "update Bootstrap file. ERR=%s\n"), db_strerror(jcr->db));
815              if (jcr->SDJobFiles != 0) {
816                 jcr->setJobStatus(JS_ErrorTerminated);
817              }
818
819          }
820          /* Start output with when and who wrote it */
821          bstrftimes(edt, sizeof(edt), time(NULL));
822          fprintf(fd, "# %s - %s - %s%s\n", edt, jcr->jr.Job,
823                  level_to_str(jcr->getJobLevel()), jcr->since);
824          for (int i=0; i < VolCount; i++) {
825             /* Write the record */
826             fprintf(fd, "Volume=\"%s\"\n", VolParams[i].VolumeName);
827             fprintf(fd, "MediaType=\"%s\"\n", VolParams[i].MediaType);
828             if (VolParams[i].Slot > 0) {
829                fprintf(fd, "Slot=%d\n", VolParams[i].Slot);
830             }
831             fprintf(fd, "VolSessionId=%u\n", jcr->VolSessionId);
832             fprintf(fd, "VolSessionTime=%u\n", jcr->VolSessionTime);
833             fprintf(fd, "VolAddr=%s-%s\n", 
834                     edit_uint64(VolParams[i].StartAddr, ed1),
835                     edit_uint64(VolParams[i].EndAddr, ed2));
836             fprintf(fd, "FileIndex=%d-%d\n", VolParams[i].FirstIndex,
837                          VolParams[i].LastIndex);
838          }
839          if (VolParams) {
840             free(VolParams);
841          }
842          if (got_pipe) {
843             close_bpipe(bpipe);
844          } else {
845             fclose(fd);
846          }
847       } else {
848          berrno be;
849          Jmsg(jcr, M_ERROR, 0, _("Could not open WriteBootstrap file:\n"
850               "%s: ERR=%s\n"), fname, be.bstrerror());
851          jcr->setJobStatus(JS_ErrorTerminated);
852       }
853       free_pool_memory(fname);
854    }
855 }