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