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