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