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