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