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