]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/backup.c
compile
[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 two of the GNU 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 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->get_JobLevel() == 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 void get_base_jobids(JCR *jcr, POOLMEM *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;
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[0]) {
129             pm_strcat(jobids, ",");
130          }
131          pm_strcat(jobids, edit_uint64(id, str_jobid));
132       }
133    }
134 }
135
136 /*
137  * Foreach files in currrent list, send "/path/fname\0LStat" to FD
138  */
139 static int accurate_list_handler(void *ctx, int num_fields, char **row)
140 {
141    JCR *jcr = (JCR *)ctx;
142
143    if (job_canceled(jcr)) {
144       return 1;
145    }
146    
147    if (row[2] > 0) {            /* discard when file_index == 0 */
148       jcr->file_bsock->fsend("%s%s%c%s", row[0], row[1], 0, row[4]); 
149    }
150    return 0;
151 }
152
153 /*
154  * Send current file list to FD
155  *    DIR -> FD : accurate files=xxxx
156  *    DIR -> FD : /path/to/file\0Lstat
157  *    DIR -> FD : /path/to/dir/\0Lstat
158  *    ...
159  *    DIR -> FD : EOD
160  */
161 bool send_accurate_current_files(JCR *jcr)
162 {
163    POOL_MEM buf;
164    bool ret=true;
165
166    if (!jcr->accurate || job_canceled(jcr)) {
167       return true;
168    }
169    /* In base level, no previous job is used */
170    if (jcr->get_JobLevel() == L_BASE) {
171       return true;
172    }
173
174    POOLMEM *nb = get_pool_memory(PM_FNAME);
175    POOLMEM *jobids = get_pool_memory(PM_FNAME);
176    nb[0] = jobids[0] = '\0';
177
178    get_base_jobids(jcr, jobids);
179
180    /* On Full mode, if no previous base job, no accurate things */
181    if (jcr->get_JobLevel() == L_FULL && *jobids == 0) {
182       goto bail_out;
183    }
184
185    /* For Incr/Diff level, we search for older jobs */
186    if (jcr->get_JobLevel() != L_FULL) {
187       db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, jobids);
188
189       if (*jobids == 0) {
190          ret=false;
191          Jmsg(jcr, M_FATAL, 0, _("Cannot find previous jobids.\n"));
192          goto bail_out;
193       }
194    }
195
196    if (jcr->JobId) {            /* display the message only for real jobs */
197       Jmsg(jcr, M_INFO, 0, _("Sending Accurate information.\n"));
198    }
199
200    /* to be able to allocate the right size for htable */
201    Mmsg(buf, "SELECT sum(JobFiles) FROM Job WHERE JobId IN (%s)",jobids);
202    db_sql_query(jcr->db, buf.c_str(), db_get_int_handler, nb);
203    Dmsg2(200, "jobids=%s nb=%s\n", jobids, nb);
204    jcr->file_bsock->fsend("accurate files=%s\n", nb); 
205
206    if (!db_open_batch_connexion(jcr, jcr->db)) {
207       ret = false;
208       Jmsg0(jcr, M_FATAL, 0, "Can't get dedicate sql connexion");
209       goto bail_out;
210    }
211
212    db_get_file_list(jcr, jcr->db_batch, jobids, accurate_list_handler, (void *)jcr);
213
214    /* TODO: close the batch connexion ? (can be used very soon) */
215
216    jcr->file_bsock->signal(BNET_EOD);
217
218 bail_out:
219    free_pool_memory(jobids);
220    free_pool_memory(nb);
221
222    return ret;
223 }
224
225 /*
226  * Do a backup of the specified FileSet
227  *
228  *  Returns:  false on failure
229  *            true  on success
230  */
231 bool do_backup(JCR *jcr)
232 {
233    int stat;
234    int tls_need = BNET_TLS_NONE;
235    BSOCK   *fd;
236    STORE *store;
237    char ed1[100];
238
239    if (jcr->get_JobLevel() == L_VIRTUAL_FULL) {
240       return do_vbackup(jcr);
241    }
242
243    /* Print Job Start message */
244    Jmsg(jcr, M_INFO, 0, _("Start Backup JobId %s, Job=%s\n"),
245         edit_uint64(jcr->JobId, ed1), jcr->Job);
246
247    set_jcr_job_status(jcr, JS_Running);
248    Dmsg2(100, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
249    if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
250       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
251       return false;
252    }
253
254    /*
255     * Open a message channel connection with the Storage
256     * daemon. This is to let him know that our client
257     * will be contacting him for a backup  session.
258     *
259     */
260    Dmsg0(110, "Open connection with storage daemon\n");
261    set_jcr_job_status(jcr, JS_WaitSD);
262    /*
263     * Start conversation with Storage daemon
264     */
265    if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
266       return false;
267    }
268    /*
269     * Now start a job with the Storage daemon
270     */
271    if (!start_storage_daemon_job(jcr, NULL, jcr->wstorage)) {
272       return false;
273    }
274
275    /*
276     * Start the job prior to starting the message thread below
277     * to avoid two threads from using the BSOCK structure at
278     * the same time.
279     */
280    if (!bnet_fsend(jcr->store_bsock, "run")) {
281       return false;
282    }
283
284    /*
285     * Now start a Storage daemon message thread.  Note,
286     *   this thread is used to provide the catalog services
287     *   for the backup job, including inserting the attributes
288     *   into the catalog.  See catalog_update() in catreq.c
289     */
290    if (!start_storage_daemon_message_thread(jcr)) {
291       return false;
292    }
293    Dmsg0(150, "Storage daemon connection OK\n");
294
295    set_jcr_job_status(jcr, JS_WaitFD);
296    if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
297       goto bail_out;
298    }
299
300    set_jcr_job_status(jcr, JS_Running);
301    fd = jcr->file_bsock;
302
303    if (!send_include_list(jcr)) {
304       goto bail_out;
305    }
306
307    if (!send_exclude_list(jcr)) {
308       goto bail_out;
309    }
310
311    if (!send_level_command(jcr)) {
312       goto bail_out;
313    }
314
315    /*
316     * send Storage daemon address to the File daemon
317     */
318    store = jcr->wstore;
319    if (store->SDDport == 0) {
320       store->SDDport = store->SDport;
321    }
322
323    /* TLS Requirement */
324    if (store->tls_enable) {
325       if (store->tls_require) {
326          tls_need = BNET_TLS_REQUIRED;
327       } else {
328          tls_need = BNET_TLS_OK;
329       }
330    }
331
332    fd->fsend(storaddr, store->address, store->SDDport, tls_need);
333    if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
334       goto bail_out;
335    }
336
337    if (!send_runscripts_commands(jcr)) {
338       goto bail_out;
339    }
340
341    /*    
342     * We re-update the job start record so that the start
343     *  time is set after the run before job.  This avoids 
344     *  that any files created by the run before job will
345     *  be saved twice.  They will be backed up in the current
346     *  job, but not in the next one unless they are changed.
347     *  Without this, they will be backed up in this job and
348     *  in the next job run because in that case, their date 
349     *   is after the start of this run.
350     */
351    jcr->start_time = time(NULL);
352    jcr->jr.StartTime = jcr->start_time;
353    if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
354       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
355    }
356
357    /*
358     * If backup is in accurate mode, we send the list of
359     * all files to FD.
360     */
361    if (!send_accurate_current_files(jcr)) {
362       goto bail_out;
363    }
364
365    /* Send backup command */
366    fd->fsend(backupcmd);
367    if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) {
368       goto bail_out;
369    }
370
371    /* Pickup Job termination data */
372    stat = wait_for_job_termination(jcr);
373    db_write_batch_file_records(jcr);    /* used by bulk batch file insert */
374    if (stat == JS_Terminated) {
375       backup_cleanup(jcr, stat);
376       return true;
377    }     
378    return false;
379
380 /* Come here only after starting SD thread */
381 bail_out:
382    set_jcr_job_status(jcr, JS_ErrorTerminated);
383    Dmsg1(400, "wait for sd. use=%d\n", jcr->use_count());
384    /* Cancel SD */
385    wait_for_job_termination(jcr, FDConnectTimeout);
386    Dmsg1(400, "after wait for sd. use=%d\n", jcr->use_count());
387    return false;
388 }
389
390
391 /*
392  * Here we wait for the File daemon to signal termination,
393  *   then we wait for the Storage daemon.  When both
394  *   are done, we return the job status.
395  * Also used by restore.c
396  */
397 int wait_for_job_termination(JCR *jcr, int timeout)
398 {
399    int32_t n = 0;
400    BSOCK *fd = jcr->file_bsock;
401    bool fd_ok = false;
402    uint32_t JobFiles, JobErrors;
403    uint32_t JobWarnings = 0;
404    uint64_t ReadBytes = 0;
405    uint64_t JobBytes = 0;
406    int VSS = 0;
407    int Encrypt = 0;
408    btimer_t *tid=NULL;
409
410    set_jcr_job_status(jcr, JS_Running);
411
412    if (fd) {
413       if (timeout) {
414          tid = start_bsock_timer(fd, timeout); /* TODO: New timeout directive??? */
415       }
416       /* Wait for Client to terminate */
417       while ((n = bget_dirmsg(fd)) >= 0) {
418          if (!fd_ok && 
419              (sscanf(fd->msg, EndJob, &jcr->FDJobStatus, &JobFiles,
420                      &ReadBytes, &JobBytes, &JobErrors, &VSS, &Encrypt) == 7 ||
421               sscanf(fd->msg, OldEndJob, &jcr->FDJobStatus, &JobFiles,
422                      &ReadBytes, &JobBytes, &JobErrors) == 5)) {
423             fd_ok = true;
424             set_jcr_job_status(jcr, jcr->FDJobStatus);
425             Dmsg1(100, "FDStatus=%c\n", (char)jcr->JobStatus);
426          } else {
427             Jmsg(jcr, M_WARNING, 0, _("Unexpected Client Job message: %s\n"),
428                  fd->msg);
429          }
430          if (job_canceled(jcr)) {
431             break;
432          }
433       }
434       if (tid) {
435          stop_bsock_timer(tid);
436       }
437
438       if (is_bnet_error(fd)) {
439          Jmsg(jcr, M_FATAL, 0, _("Network error with FD during %s: ERR=%s\n"),
440               job_type_to_str(jcr->get_JobType()), fd->bstrerror());
441       }
442       fd->signal(BNET_TERMINATE);   /* tell Client we are terminating */
443    }
444
445    /* Force cancel in SD if failing */
446    if (job_canceled(jcr) || !fd_ok) {
447       cancel_storage_daemon_job(jcr);
448    }
449
450    /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
451    wait_for_storage_daemon_termination(jcr);
452
453    /* Return values from FD */
454    if (fd_ok) {
455       jcr->JobFiles = JobFiles;
456       jcr->JobErrors += JobErrors;       /* Keep total errors */
457       jcr->ReadBytes = ReadBytes;
458       jcr->JobBytes = JobBytes;
459       jcr->JobWarnings = JobWarnings;
460       jcr->VSS = VSS;
461       jcr->Encrypt = Encrypt;
462    } else {
463       Jmsg(jcr, M_FATAL, 0, _("No Job status returned from FD.\n"));
464    }
465
466 // Dmsg4(100, "fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", fd_ok, jcr->FDJobStatus,
467 //   jcr->JobStatus, jcr->SDJobStatus);
468
469    /* Return the first error status we find Dir, FD, or SD */
470    if (!fd_ok || is_bnet_error(fd)) { /* if fd not set, that use !fd_ok */
471       jcr->FDJobStatus = JS_ErrorTerminated;
472    }
473    if (jcr->JobStatus != JS_Terminated) {
474       return jcr->JobStatus;
475    }
476    if (jcr->FDJobStatus != JS_Terminated) {
477       return jcr->FDJobStatus;
478    }
479    return jcr->SDJobStatus;
480 }
481
482 /*
483  * Release resources allocated during backup.
484  */
485 void backup_cleanup(JCR *jcr, int TermCode)
486 {
487    char sdt[50], edt[50], schedt[50];
488    char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], compress[50];
489    char ec6[30], ec7[30], ec8[30], elapsed[50];
490    char term_code[100], fd_term_msg[100], sd_term_msg[100];
491    const char *term_msg;
492    int msg_type = M_INFO;
493    MEDIA_DBR mr;
494    CLIENT_DBR cr;
495    double kbps, compression;
496    utime_t RunTime;
497
498    if (jcr->get_JobLevel() == L_VIRTUAL_FULL) {
499       vbackup_cleanup(jcr, TermCode);
500       return;
501    }
502
503    Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
504    memset(&mr, 0, sizeof(mr));
505    memset(&cr, 0, sizeof(cr));
506
507    update_job_end(jcr, TermCode);
508
509    if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
510       Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
511          db_strerror(jcr->db));
512       set_jcr_job_status(jcr, JS_ErrorTerminated);
513    }
514
515    bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
516    if (!db_get_client_record(jcr, jcr->db, &cr)) {
517       Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
518          db_strerror(jcr->db));
519    }
520
521    bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
522    if (!db_get_media_record(jcr, jcr->db, &mr)) {
523       Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
524          mr.VolumeName, db_strerror(jcr->db));
525       set_jcr_job_status(jcr, JS_ErrorTerminated);
526    }
527
528    update_bootstrap_file(jcr);
529
530    switch (jcr->JobStatus) {
531       case JS_Terminated:
532          if (jcr->JobErrors || jcr->SDErrors) {
533             term_msg = _("Backup OK -- with warnings");
534          } else {
535             term_msg = _("Backup OK");
536          }
537          break;
538       case JS_Warnings:
539          term_msg = _("Backup OK -- with warnings");
540          break;
541       case JS_FatalError:
542       case JS_ErrorTerminated:
543          term_msg = _("*** Backup Error ***");
544          msg_type = M_ERROR;          /* Generate error message */
545          if (jcr->store_bsock) {
546             jcr->store_bsock->signal(BNET_TERMINATE);
547             if (jcr->SD_msg_chan) {
548                pthread_cancel(jcr->SD_msg_chan);
549             }
550          }
551          break;
552       case JS_Canceled:
553          term_msg = _("Backup Canceled");
554          if (jcr->store_bsock) {
555             jcr->store_bsock->signal(BNET_TERMINATE);
556             if (jcr->SD_msg_chan) {
557                pthread_cancel(jcr->SD_msg_chan);
558             }
559          }
560          break;
561       default:
562          term_msg = term_code;
563          sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
564          break;
565    }
566    bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
567    bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
568    bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
569    RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
570    if (RunTime <= 0) {
571       kbps = 0;
572    } else {
573       kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
574    }
575    if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
576       /*
577        * Note, if the job has erred, most likely it did not write any
578        *  tape, so suppress this "error" message since in that case
579        *  it is normal.  Or look at it the other way, only for a
580        *  normal exit should we complain about this error.
581        */
582       if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
583          Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
584       }
585       jcr->VolumeName[0] = 0;         /* none */
586    }
587
588    if (jcr->ReadBytes == 0) {
589       bstrncpy(compress, "None", sizeof(compress));
590    } else {
591       compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
592       if (compression < 0.5) {
593          bstrncpy(compress, "None", sizeof(compress));
594       } else {
595          bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
596       }
597    }
598    jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
599    jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
600
601 // bmicrosleep(15, 0);                /* for debugging SIGHUP */
602
603    Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
604 "  Build OS:               %s %s %s\n"
605 "  JobId:                  %d\n"
606 "  Job:                    %s\n"
607 "  Backup Level:           %s%s\n"
608 "  Client:                 \"%s\" %s\n"
609 "  FileSet:                \"%s\" %s\n"
610 "  Pool:                   \"%s\" (From %s)\n"
611 "  Catalog:                \"%s\" (From %s)\n"
612 "  Storage:                \"%s\" (From %s)\n"
613 "  Scheduled time:         %s\n"
614 "  Start time:             %s\n"
615 "  End time:               %s\n"
616 "  Elapsed time:           %s\n"
617 "  Priority:               %d\n"
618 "  FD Files Written:       %s\n"
619 "  SD Files Written:       %s\n"
620 "  FD Bytes Written:       %s (%sB)\n"
621 "  SD Bytes Written:       %s (%sB)\n"
622 "  Rate:                   %.1f KB/s\n"
623 "  Software Compression:   %s\n"
624 "  VSS:                    %s\n"
625 "  Encryption:             %s\n"
626 "  Accurate:               %s\n"
627 "  Volume name(s):         %s\n"
628 "  Volume Session Id:      %d\n"
629 "  Volume Session Time:    %d\n"
630 "  Last Volume Bytes:      %s (%sB)\n"
631 "  Non-fatal FD errors:    %d\n"
632 "  SD Errors:              %d\n"
633 "  FD termination status:  %s\n"
634 "  SD termination status:  %s\n"
635 "  Termination:            %s\n\n"),
636         BACULA, my_name, VERSION, LSMDATE, edt,
637         HOST_OS, DISTNAME, DISTVER,
638         jcr->jr.JobId,
639         jcr->jr.Job,
640         level_to_str(jcr->get_JobLevel()), jcr->since,
641         jcr->client->name(), cr.Uname,
642         jcr->fileset->name(), jcr->FSCreateTime,
643         jcr->pool->name(), jcr->pool_source,
644         jcr->catalog->name(), jcr->catalog_source,
645         jcr->wstore->name(), jcr->wstore_source,
646         schedt,
647         sdt,
648         edt,
649         edit_utime(RunTime, elapsed, sizeof(elapsed)),
650         jcr->JobPriority,
651         edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
652         edit_uint64_with_commas(jcr->SDJobFiles, ec2),
653         edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
654         edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
655         edit_uint64_with_commas(jcr->SDJobBytes, ec5),
656         edit_uint64_with_suffix(jcr->SDJobBytes, ec6),
657         kbps,
658         compress,
659         jcr->VSS?_("yes"):_("no"),
660         jcr->Encrypt?_("yes"):_("no"),
661         jcr->accurate?_("yes"):_("no"),
662         jcr->VolumeName,
663         jcr->VolSessionId,
664         jcr->VolSessionTime,
665         edit_uint64_with_commas(mr.VolBytes, ec7),
666         edit_uint64_with_suffix(mr.VolBytes, ec8),
667         jcr->JobErrors,
668         jcr->SDErrors,
669         fd_term_msg,
670         sd_term_msg,
671         term_msg);
672
673    Dmsg0(100, "Leave backup_cleanup()\n");
674 }
675
676 void update_bootstrap_file(JCR *jcr)
677 {
678    /* Now update the bootstrap file if any */
679    if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes &&
680        jcr->job->WriteBootstrap) {
681       FILE *fd;
682       BPIPE *bpipe = NULL;
683       int got_pipe = 0;
684       POOLMEM *fname = get_pool_memory(PM_FNAME);
685       fname = edit_job_codes(jcr, fname, jcr->job->WriteBootstrap, "");
686
687       VOL_PARAMS *VolParams = NULL;
688       int VolCount;
689       char edt[50], ed1[50], ed2[50];
690
691       if (*fname == '|') {
692          got_pipe = 1;
693          bpipe = open_bpipe(fname+1, 0, "w"); /* skip first char "|" */
694          fd = bpipe ? bpipe->wfd : NULL;
695       } else {
696          /* ***FIXME*** handle BASE */
697          fd = fopen(fname, jcr->get_JobLevel()==L_FULL?"w+b":"a+b");
698       }
699       if (fd) {
700          VolCount = db_get_job_volume_parameters(jcr, jcr->db, jcr->JobId,
701                     &VolParams);
702          if (VolCount == 0) {
703             Jmsg(jcr, M_ERROR, 0, _("Could not get Job Volume Parameters to "
704                  "update Bootstrap file. ERR=%s\n"), db_strerror(jcr->db));
705              if (jcr->SDJobFiles != 0) {
706                 set_jcr_job_status(jcr, JS_ErrorTerminated);
707              }
708
709          }
710          /* Start output with when and who wrote it */
711          bstrftimes(edt, sizeof(edt), time(NULL));
712          fprintf(fd, "# %s - %s - %s%s\n", edt, jcr->jr.Job,
713                  level_to_str(jcr->get_JobLevel()), jcr->since);
714          for (int i=0; i < VolCount; i++) {
715             /* Write the record */
716             fprintf(fd, "Volume=\"%s\"\n", VolParams[i].VolumeName);
717             fprintf(fd, "MediaType=\"%s\"\n", VolParams[i].MediaType);
718             if (VolParams[i].Slot > 0) {
719                fprintf(fd, "Slot=%d\n", VolParams[i].Slot);
720             }
721             fprintf(fd, "VolSessionId=%u\n", jcr->VolSessionId);
722             fprintf(fd, "VolSessionTime=%u\n", jcr->VolSessionTime);
723             fprintf(fd, "VolAddr=%s-%s\n", 
724                     edit_uint64(VolParams[i].StartAddr, ed1),
725                     edit_uint64(VolParams[i].EndAddr, ed2));
726             fprintf(fd, "FileIndex=%d-%d\n", VolParams[i].FirstIndex,
727                          VolParams[i].LastIndex);
728          }
729          if (VolParams) {
730             free(VolParams);
731          }
732          if (got_pipe) {
733             close_bpipe(bpipe);
734          } else {
735             fclose(fd);
736          }
737       } else {
738          berrno be;
739          Jmsg(jcr, M_ERROR, 0, _("Could not open WriteBootstrap file:\n"
740               "%s: ERR=%s\n"), fname, be.bstrerror());
741          set_jcr_job_status(jcr, JS_ErrorTerminated);
742       }
743       free_pool_memory(fname);
744    }
745 }