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