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