]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/backup.c
Use max(FileIndex) for restarting incomplete Job
[bacula/bacula] / bacula / src / dird / backup.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2011 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 three of the GNU Affero 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 Affero 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  */
42
43 #include "bacula.h"
44 #include "dird.h"
45 #include "ua.h"
46
47 /* Commands sent to File daemon */
48 static char backupcmd[] = "backup FileIndex=%ld\n";
49 static char storaddr[]  = "storage address=%s port=%d ssl=%d\n";
50
51 /* Responses received from File daemon */
52 static char OKbackup[]   = "2000 OK backup\n";
53 static char OKstore[]    = "2000 OK storage\n";
54 static char EndJob[]     = "2800 End Job TermCode=%d JobFiles=%u "
55                            "ReadBytes=%llu JobBytes=%llu Errors=%u "  
56                            "VSS=%d Encrypt=%d\n";
57 /* Pre 1.39.29 (04Dec06) EndJob */
58 static char OldEndJob[]  = "2800 End Job TermCode=%d JobFiles=%u "
59                            "ReadBytes=%llu JobBytes=%llu Errors=%u\n";
60 /* 
61  * Called here before the job is run to do the job
62  *   specific setup.
63  */
64 bool do_backup_init(JCR *jcr)
65 {
66
67    if (jcr->is_JobLevel(L_VIRTUAL_FULL)) {
68       return do_vbackup_init(jcr);
69    }
70    free_rstorage(jcr);                   /* we don't read so release */
71
72    if (!get_or_create_fileset_record(jcr)) {
73       return false;
74    }
75
76    /* 
77     * Get definitive Job level and since time
78     */
79    get_level_since_time(jcr, jcr->since, sizeof(jcr->since));
80
81    apply_pool_overrides(jcr);
82
83    if (!allow_duplicate_job(jcr)) {
84       return false;
85    }
86
87    jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->name());
88    if (jcr->jr.PoolId == 0) {
89       return false;
90    }
91
92    /* If pool storage specified, use it instead of job storage */
93    copy_wstorage(jcr, jcr->pool->storage, _("Pool resource"));
94
95    if (!jcr->wstorage) {
96       Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Job or Pool.\n"));
97       return false;
98    }
99
100    create_clones(jcr);                /* run any clone jobs */
101
102    return true;
103 }
104
105 /* Take all base jobs from job resource and find the
106  * last L_BASE jobid.
107  */
108 static bool get_base_jobids(JCR *jcr, db_list_ctx *jobids)
109 {
110    JOB_DBR jr;
111    JOB *job;
112    JobId_t id;
113    char str_jobid[50];
114
115    if (!jcr->job->base) {
116       return false;             /* no base job, stop accurate */
117    }
118
119    memset(&jr, 0, sizeof(JOB_DBR));
120    jr.StartTime = jcr->jr.StartTime;
121
122    foreach_alist(job, jcr->job->base) {
123       bstrncpy(jr.Name, job->name(), sizeof(jr.Name));
124       db_get_base_jobid(jcr, jcr->db, &jr, &id);
125
126       if (id) {
127          if (jobids->count) {
128             pm_strcat(jobids->list, ",");
129          }
130          pm_strcat(jobids->list, edit_uint64(id, str_jobid));
131          jobids->count++;
132       }
133    }
134
135    return jobids->count > 0;
136 }
137
138 /*
139  * Foreach files in currrent list, send "/path/fname\0LStat\0MD5\0Delta" to FD
140  *      row[0]=Path, row[1]=Filename, row[2]=FileIndex
141  *      row[3]=JobId row[4]=LStat row[5]=MarkId row[6]=MD5
142  */
143 static int accurate_list_handler(void *ctx, int num_fields, char **row)
144 {
145    JCR *jcr = (JCR *)ctx;
146
147    if (job_canceled(jcr)) {
148       return 1;
149    }
150    
151    if (row[2][0] == '0') {           /* discard when file_index == 0 */
152       return 0;
153    }
154
155    /* sending with checksum */
156    if (jcr->use_accurate_chksum 
157        && num_fields == 7 
158        && row[6][0] /* skip checksum = '0' */
159        && row[6][1])
160    { 
161       jcr->file_bsock->fsend("%s%s%c%s%c%s%c%s", 
162                              row[0], row[1], 0, row[4], 0, row[6], 0, row[5]); 
163    } else {
164       jcr->file_bsock->fsend("%s%s%c%s%c%c%s", 
165                              row[0], row[1], 0, row[4], 0, 0, row[5]); 
166    }
167    return 0;
168 }
169
170 /* In this procedure, we check if the current fileset is using checksum
171  * FileSet-> Include-> Options-> Accurate/Verify/BaseJob=checksum
172  * This procedure uses jcr->HasBase, so it must be call after the initialization
173  */
174 static bool is_checksum_needed_by_fileset(JCR *jcr)
175 {
176    FILESET *f;
177    INCEXE *inc;
178    FOPTS *fopts;
179    bool in_block=false;
180    bool have_basejob_option=false;
181    if (!jcr->job || !jcr->job->fileset) {
182       return false;
183    }
184
185    f = jcr->job->fileset;
186    
187    for (int i=0; i < f->num_includes; i++) { /* Parse all Include {} */
188       inc = f->include_items[i];
189       
190       for (int j=0; j < inc->num_opts; j++) { /* Parse all Options {} */
191          fopts = inc->opts_list[j];
192          
193          for (char *k=fopts->opts; *k ; k++) { /* Try to find one request */
194             switch (*k) {
195             case 'V':           /* verify */
196                in_block = (jcr->getJobType() == JT_VERIFY); /* not used now */
197                break;
198             case 'J':           /* Basejob keyword */
199                have_basejob_option = in_block = jcr->HasBase;
200                break;
201             case 'C':           /* Accurate keyword */
202                in_block = !jcr->is_JobLevel(L_FULL);
203                break;
204             case ':':           /* End of keyword */
205                in_block = false;
206                break;
207             case '5':           /* MD5  */
208             case '1':           /* SHA1 */
209                if (in_block) {
210                   Dmsg0(50, "Checksum will be sent to FD\n");
211                   return true;
212                }
213                break;
214             default:
215                break;
216             }
217          }
218       }
219    }
220
221    /* By default for BaseJobs, we send the checksum */
222    if (!have_basejob_option && jcr->HasBase) {
223       return true;
224    }
225    
226    Dmsg0(50, "Checksum will be sent to FD\n");
227    return false;
228 }
229
230 /*
231  * Send current file list to FD
232  *    DIR -> FD : accurate files=xxxx
233  *    DIR -> FD : /path/to/file\0Lstat\0MD5\0Delta
234  *    DIR -> FD : /path/to/dir/\0Lstat\0MD5\0Delta
235  *    ...
236  *    DIR -> FD : EOD
237  */
238 bool send_accurate_current_files(JCR *jcr)
239 {
240    POOL_MEM buf;
241    db_list_ctx jobids;
242    db_list_ctx nb;
243    char ed1[50];
244
245    /* For incomplete Jobs, we add our own id */
246    if (jcr->incomplete) {
247       edit_int64(jcr->JobId, ed1);   
248       jobids.add(ed1);
249    } else {
250       if (!jcr->accurate || job_canceled(jcr)) {
251          return true;
252       }
253       /* In base level, no previous job is used */
254       if (jcr->is_JobLevel(L_BASE)) {
255          return true;
256       }
257    
258       if (jcr->is_JobLevel(L_FULL)) {
259          /* On Full mode, if no previous base job, no accurate things */
260          if (!get_base_jobids(jcr, &jobids)) {
261             return true;
262          }
263          jcr->HasBase = true;
264          Jmsg(jcr, M_INFO, 0, _("Using BaseJobId(s): %s\n"), jobids.list);
265
266       } else {
267          /* For Incr/Diff level, we search for older jobs */
268          db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, &jobids);
269
270          /* We are in Incr/Diff, but no Full to build the accurate list... */
271          if (jobids.count == 0) {
272             Jmsg(jcr, M_FATAL, 0, _("Cannot find previous jobids.\n"));
273             return false;  /* fail */
274          }
275       }
276    }
277
278    /* Don't send and store the checksum if fileset doesn't require it */
279    jcr->use_accurate_chksum = is_checksum_needed_by_fileset(jcr);
280
281    if (jcr->JobId) {            /* display the message only for real jobs */
282       Jmsg(jcr, M_INFO, 0, _("Sending Accurate information.\n"));
283    }
284
285    /* to be able to allocate the right size for htable */
286    Mmsg(buf, "SELECT sum(JobFiles) FROM Job WHERE JobId IN (%s)", jobids.list);
287    db_sql_query(jcr->db, buf.c_str(), db_list_handler, &nb);
288    Dmsg2(200, "jobids=%s nb=%s\n", jobids.list, nb.list);
289    jcr->file_bsock->fsend("accurate files=%s\n", nb.list); 
290
291    if (!db_open_batch_connexion(jcr, jcr->db)) {
292       Jmsg0(jcr, M_FATAL, 0, "Can't get batch sql connexion");
293       return false;  /* Fail */
294    }
295    
296    if (jcr->HasBase) {
297       jcr->nb_base_files = str_to_int64(nb.list);
298       db_create_base_file_list(jcr, jcr->db, jobids.list);
299       db_get_base_file_list(jcr, jcr->db, jcr->use_accurate_chksum,
300                             accurate_list_handler, (void *)jcr);
301
302    } else {
303       db_get_file_list(jcr, jcr->db_batch,
304                        jobids.list, jcr->use_accurate_chksum, false /* no delta */,
305                        accurate_list_handler, (void *)jcr);
306    } 
307
308    /* TODO: close the batch connection ? (can be used very soon) */
309
310    jcr->file_bsock->signal(BNET_EOD);
311    return true;
312 }
313
314 /*
315  * Do a backup of the specified FileSet
316  *
317  *  Returns:  false on failure
318  *            true  on success
319  */
320 bool do_backup(JCR *jcr)
321 {
322    int stat;
323    int tls_need = BNET_TLS_NONE;
324    BSOCK   *fd;
325    STORE *store;
326    char ed1[100];
327    db_int64_ctx job;
328    POOL_MEM buf;
329
330    if (jcr->is_JobLevel(L_VIRTUAL_FULL)) {
331       return do_vbackup(jcr);
332    }
333
334    /* Print Job Start message */
335    Jmsg(jcr, M_INFO, 0, _("Start Backup JobId %s, Job=%s\n"),
336         edit_uint64(jcr->JobId, ed1), jcr->Job);
337
338    jcr->setJobStatus(JS_Running);
339    Dmsg2(100, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
340    if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
341       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
342       return false;
343    }
344
345    /* For incomplete Jobs, we add our own id */
346    if (jcr->incomplete) {
347       edit_int64(jcr->JobId, ed1);   
348       Mmsg(buf, "SELECT max(FileIndex) FROM File WHERE JobId=%s", ed1);
349       if (!db_sql_query(jcr->db, buf.c_str(), db_int64_handler, &job)) {
350          Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
351          return false;
352       }
353       jcr->JobFiles = job.value;
354       Mmsg(buf, "SELECT VolSessionId FROM Job WHERE JobId=%s", ed1);
355       if (!db_sql_query(jcr->db, buf.c_str(), db_int64_handler, &job)) {
356          Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
357          return false;
358       }
359       jcr->VolSessionId = job.value;
360       Mmsg(buf, "SELECT VolSessionTime FROM Job WHERE JobId=%s", ed1);
361       if (!db_sql_query(jcr->db, buf.c_str(), db_int64_handler, &job)) {
362          Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
363          return false;
364       }
365       jcr->VolSessionTime = job.value;
366       Dmsg4(100, "JobId=%s JobFiles=%ld VolSessionId=%ld VolSessionTime=%ld\n", ed1, 
367             jcr->JobFiles, jcr->VolSessionId, jcr->VolSessionTime);
368    }
369
370    /*
371     * Open a message channel connection with the Storage
372     * daemon. This is to let him know that our client
373     * will be contacting him for a backup  session.
374     *
375     */
376    Dmsg0(110, "Open connection with storage daemon\n");
377    jcr->setJobStatus(JS_WaitSD);
378    /*
379     * Start conversation with Storage daemon
380     */
381    if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
382       return false;
383    }
384    /*
385     * Now start a job with the Storage daemon
386     */
387    if (!start_storage_daemon_job(jcr, NULL, jcr->wstorage)) {
388       return false;
389    }
390
391    /*
392     * Start the job prior to starting the message thread below
393     * to avoid two threads from using the BSOCK structure at
394     * the same time.
395     */
396    if (!jcr->store_bsock->fsend("run")) {
397       return false;
398    }
399
400    /*
401     * Now start a Storage daemon message thread.  Note,
402     *   this thread is used to provide the catalog services
403     *   for the backup job, including inserting the attributes
404     *   into the catalog.  See catalog_update() in catreq.c
405     */
406    if (!start_storage_daemon_message_thread(jcr)) {
407       return false;
408    }
409    Dmsg0(150, "Storage daemon connection OK\n");
410
411    jcr->setJobStatus(JS_WaitFD);
412    if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
413       goto bail_out;
414    }
415
416    jcr->setJobStatus(JS_Running);
417    fd = jcr->file_bsock;
418
419    if (!send_include_list(jcr)) {
420       goto bail_out;
421    }
422
423    if (!send_exclude_list(jcr)) {
424       goto bail_out;
425    }
426
427    if (!send_level_command(jcr)) {
428       goto bail_out;
429    }
430
431    /* TODO: See priority with bandwidth parameter */
432    if (jcr->job->max_bandwidth > 0) {
433       jcr->max_bandwidth = jcr->job->max_bandwidth;
434    } else if (jcr->client->max_bandwidth > 0) {
435       jcr->max_bandwidth = jcr->client->max_bandwidth;
436    }
437
438    if (jcr->max_bandwidth > 0) {
439       send_bwlimit(jcr, jcr->Job); /* Old clients don't have this command */
440    }
441
442    /*
443     * send Storage daemon address to the File daemon
444     */
445    store = jcr->wstore;
446    if (store->SDDport == 0) {
447       store->SDDport = store->SDport;
448    }
449
450    /* TLS Requirement */
451    if (store->tls_enable) {
452       if (store->tls_require) {
453          tls_need = BNET_TLS_REQUIRED;
454       } else {
455          tls_need = BNET_TLS_OK;
456       }
457    }
458
459    fd->fsend(storaddr, store->address, store->SDDport, tls_need);
460    if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
461       goto bail_out;
462    }
463
464    if (!send_runscripts_commands(jcr)) {
465       goto bail_out;
466    }
467
468    /*    
469     * We re-update the job start record so that the start
470     *  time is set after the run before job.  This avoids 
471     *  that any files created by the run before job will
472     *  be saved twice.  They will be backed up in the current
473     *  job, but not in the next one unless they are changed.
474     *  Without this, they will be backed up in this job and
475     *  in the next job run because in that case, their date 
476     *   is after the start of this run.
477     */
478    jcr->start_time = time(NULL);
479    jcr->jr.StartTime = jcr->start_time;
480    if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
481       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
482    }
483
484    /*
485     * If backup is in accurate mode, we send the list of
486     * all files to FD.
487     */
488    if (!send_accurate_current_files(jcr)) {
489       goto bail_out;     /* error */
490    }
491
492    /* Send backup command */
493    fd->fsend(backupcmd, jcr->JobFiles);
494    Dmsg1(100, ">filed: %s", fd->msg);
495    if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) {
496       goto bail_out;
497    }
498
499    /* Pickup Job termination data */
500    stat = wait_for_job_termination(jcr);
501    db_write_batch_file_records(jcr);    /* used by bulk batch file insert */
502
503    if (jcr->HasBase && !db_commit_base_file_attributes_record(jcr, jcr->db))  {
504       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
505    }
506
507    if (stat == JS_Terminated) {
508       backup_cleanup(jcr, stat);
509       return true;
510    }     
511    return false;
512
513 /* Come here only after starting SD thread */
514 bail_out:
515    jcr->setJobStatus(JS_ErrorTerminated);
516    Dmsg1(400, "wait for sd. use=%d\n", jcr->use_count());
517    /* Cancel SD */
518    wait_for_job_termination(jcr, FDConnectTimeout);
519    Dmsg1(400, "after wait for sd. use=%d\n", jcr->use_count());
520    return false;
521 }
522
523
524 /*
525  * Here we wait for the File daemon to signal termination,
526  *   then we wait for the Storage daemon.  When both
527  *   are done, we return the job status.
528  * Also used by restore.c
529  */
530 int wait_for_job_termination(JCR *jcr, int timeout)
531 {
532    int32_t n = 0;
533    BSOCK *fd = jcr->file_bsock;
534    bool fd_ok = false;
535    uint32_t JobFiles, JobErrors;
536    uint32_t JobWarnings = 0;
537    uint64_t ReadBytes = 0;
538    uint64_t JobBytes = 0;
539    int VSS = 0;
540    int Encrypt = 0;
541    btimer_t *tid=NULL;
542
543    jcr->setJobStatus(JS_Running);
544
545    if (fd) {
546       if (timeout) {
547          tid = start_bsock_timer(fd, timeout); /* TODO: New timeout directive??? */
548       }
549       /* Wait for Client to terminate */
550       while ((n = bget_dirmsg(fd)) >= 0) {
551          if (!fd_ok && 
552              (sscanf(fd->msg, EndJob, &jcr->FDJobStatus, &JobFiles,
553                      &ReadBytes, &JobBytes, &JobErrors, &VSS, &Encrypt) == 7 ||
554               sscanf(fd->msg, OldEndJob, &jcr->FDJobStatus, &JobFiles,
555                      &ReadBytes, &JobBytes, &JobErrors) == 5)) {
556             fd_ok = true;
557             jcr->setJobStatus(jcr->FDJobStatus);
558             Dmsg1(100, "FDStatus=%c\n", (char)jcr->JobStatus);
559          } else {
560             Jmsg(jcr, M_WARNING, 0, _("Unexpected Client Job message: %s\n"),
561                  fd->msg);
562          }
563          if (job_canceled(jcr)) {
564             break;
565          }
566       }
567       if (tid) {
568          stop_bsock_timer(tid);
569       }
570
571       if (is_bnet_error(fd)) {
572          Jmsg(jcr, M_FATAL, 0, _("Network error with FD during %s: ERR=%s\n"),
573               job_type_to_str(jcr->getJobType()), fd->bstrerror());
574       }
575       fd->signal(BNET_TERMINATE);   /* tell Client we are terminating */
576    }
577
578    /*
579     * Force cancel in SD if failing, but not for Incomplete jobs
580     *  so that we let the SD despool.
581     */
582    if (jcr->is_canceled() || !fd_ok) {
583       cancel_storage_daemon_job(jcr);
584    }
585
586    /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
587    wait_for_storage_daemon_termination(jcr);
588
589    /* Return values from FD */
590    if (fd_ok) {
591       jcr->JobFiles = JobFiles;
592       jcr->JobErrors += JobErrors;       /* Keep total errors */
593       jcr->ReadBytes = ReadBytes;
594       jcr->JobBytes = JobBytes;
595       jcr->JobWarnings = JobWarnings;
596       jcr->VSS = VSS;
597       jcr->Encrypt = Encrypt;
598    } else {
599       Jmsg(jcr, M_FATAL, 0, _("No Job status returned from FD.\n"));
600    }
601
602 // Dmsg4(100, "fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", fd_ok, jcr->FDJobStatus,
603 //   jcr->JobStatus, jcr->SDJobStatus);
604
605    /* Return the first error status we find Dir, FD, or SD */
606    if (!fd_ok || is_bnet_error(fd)) { /* if fd not set, that use !fd_ok */
607       jcr->FDJobStatus = JS_ErrorTerminated;
608    }
609    if (jcr->JobStatus != JS_Terminated) {
610       return jcr->JobStatus;
611    }
612    if (jcr->FDJobStatus != JS_Terminated) {
613       return jcr->FDJobStatus;
614    }
615    return jcr->SDJobStatus;
616 }
617
618 /*
619  * Release resources allocated during backup.
620  */
621 void backup_cleanup(JCR *jcr, int TermCode)
622 {
623    char sdt[50], edt[50], schedt[50];
624    char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], compress[50];
625    char ec6[30], ec7[30], ec8[30], elapsed[50];
626    char term_code[100], fd_term_msg[100], sd_term_msg[100];
627    const char *term_msg;
628    int msg_type = M_INFO;
629    MEDIA_DBR mr;
630    CLIENT_DBR cr;
631    double kbps, compression;
632    utime_t RunTime;
633    POOL_MEM base_info;
634
635    if (jcr->is_JobLevel(L_VIRTUAL_FULL)) {
636       vbackup_cleanup(jcr, TermCode);
637       return;
638    }
639
640    Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
641    memset(&mr, 0, sizeof(mr));
642    memset(&cr, 0, sizeof(cr));
643
644 #ifdef xxxx
645    if (jcr->getJobStatus() == JS_Terminated && 
646         (jcr->JobErrors || jcr->SDErrors || jcr->JobWarnings)) {
647       TermCode = JS_Warnings;
648    }
649 #endif
650          
651    update_job_end(jcr, TermCode);
652
653    if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
654       Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
655          db_strerror(jcr->db));
656       jcr->setJobStatus(JS_ErrorTerminated);
657    }
658
659    bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
660    if (!db_get_client_record(jcr, jcr->db, &cr)) {
661       Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
662          db_strerror(jcr->db));
663    }
664
665    bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
666    if (!db_get_media_record(jcr, jcr->db, &mr)) {
667       Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
668          mr.VolumeName, db_strerror(jcr->db));
669       jcr->setJobStatus(JS_ErrorTerminated);
670    }
671
672    update_bootstrap_file(jcr);
673
674    switch (jcr->JobStatus) {
675       case JS_Terminated:
676          if (jcr->JobErrors || jcr->SDErrors) {
677             term_msg = _("Backup OK -- with warnings");
678          } else {
679             term_msg = _("Backup OK");
680          }
681          break;
682       case JS_Incomplete:
683          term_msg = _("Backup failed -- incomplete");
684          break;
685       case JS_Warnings:
686          term_msg = _("Backup OK -- with warnings");
687          break;
688       case JS_FatalError:
689       case JS_ErrorTerminated:
690          term_msg = _("*** Backup Error ***");
691          msg_type = M_ERROR;          /* Generate error message */
692          if (jcr->store_bsock) {
693             jcr->store_bsock->signal(BNET_TERMINATE);
694             if (jcr->SD_msg_chan) {
695                pthread_cancel(jcr->SD_msg_chan);
696             }
697          }
698          break;
699       case JS_Canceled:
700          term_msg = _("Backup Canceled");
701          if (jcr->store_bsock) {
702             jcr->store_bsock->signal(BNET_TERMINATE);
703             if (jcr->SD_msg_chan) {
704                pthread_cancel(jcr->SD_msg_chan);
705             }
706          }
707          break;
708       default:
709          term_msg = term_code;
710          sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
711          break;
712    }
713    bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
714    bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
715    bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
716    RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
717    if (RunTime <= 0) {
718       kbps = 0;
719    } else {
720       kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
721    }
722    if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
723       /*
724        * Note, if the job has erred, most likely it did not write any
725        *  tape, so suppress this "error" message since in that case
726        *  it is normal.  Or look at it the other way, only for a
727        *  normal exit should we complain about this error.
728        */
729       if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
730          Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
731       }
732       jcr->VolumeName[0] = 0;         /* none */
733    }
734
735    if (jcr->ReadBytes == 0) {
736       bstrncpy(compress, "None", sizeof(compress));
737    } else {
738       compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
739       if (compression < 0.5) {
740          bstrncpy(compress, "None", sizeof(compress));
741       } else {
742          bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
743       }
744    }
745    jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
746    jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
747
748    if (jcr->HasBase) {
749       Mmsg(base_info, "  Base files/Used files:  %lld/%lld (%.2f%%)\n",
750            jcr->nb_base_files, 
751            jcr->nb_base_files_used, 
752            jcr->nb_base_files_used*100.0/jcr->nb_base_files);
753    }
754 // bmicrosleep(15, 0);                /* for debugging SIGHUP */
755
756    Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
757 "  Build OS:               %s %s %s\n"
758 "  JobId:                  %d\n"
759 "  Job:                    %s\n"
760 "  Backup Level:           %s%s\n"
761 "  Client:                 \"%s\" %s\n"
762 "  FileSet:                \"%s\" %s\n"
763 "  Pool:                   \"%s\" (From %s)\n"
764 "  Catalog:                \"%s\" (From %s)\n"
765 "  Storage:                \"%s\" (From %s)\n"
766 "  Scheduled time:         %s\n"
767 "  Start time:             %s\n"
768 "  End time:               %s\n"
769 "  Elapsed time:           %s\n"
770 "  Priority:               %d\n"
771 "  FD Files Written:       %s\n"
772 "  SD Files Written:       %s\n"
773 "  FD Bytes Written:       %s (%sB)\n"
774 "  SD Bytes Written:       %s (%sB)\n"
775 "  Rate:                   %.1f KB/s\n"
776 "  Software Compression:   %s\n"
777 "%s"                                         /* Basefile info */
778 "  VSS:                    %s\n"
779 "  Encryption:             %s\n"
780 "  Accurate:               %s\n"
781 "  Volume name(s):         %s\n"
782 "  Volume Session Id:      %d\n"
783 "  Volume Session Time:    %d\n"
784 "  Last Volume Bytes:      %s (%sB)\n"
785 "  Non-fatal FD errors:    %d\n"
786 "  SD Errors:              %d\n"
787 "  FD termination status:  %s\n"
788 "  SD termination status:  %s\n"
789 "  Termination:            %s\n\n"),
790         BACULA, my_name, VERSION, LSMDATE,
791         HOST_OS, DISTNAME, DISTVER,
792         jcr->jr.JobId,
793         jcr->jr.Job,
794         level_to_str(jcr->getJobLevel()), jcr->since,
795         jcr->client->name(), cr.Uname,
796         jcr->fileset->name(), jcr->FSCreateTime,
797         jcr->pool->name(), jcr->pool_source,
798         jcr->catalog->name(), jcr->catalog_source,
799         jcr->wstore->name(), jcr->wstore_source,
800         schedt,
801         sdt,
802         edt,
803         edit_utime(RunTime, elapsed, sizeof(elapsed)),
804         jcr->JobPriority,
805         edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
806         edit_uint64_with_commas(jcr->SDJobFiles, ec2),
807         edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
808         edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
809         edit_uint64_with_commas(jcr->SDJobBytes, ec5),
810         edit_uint64_with_suffix(jcr->SDJobBytes, ec6),
811         kbps,
812         compress,
813         base_info.c_str(),
814         jcr->VSS?_("yes"):_("no"),
815         jcr->Encrypt?_("yes"):_("no"),
816         jcr->accurate?_("yes"):_("no"),
817         jcr->VolumeName,
818         jcr->VolSessionId,
819         jcr->VolSessionTime,
820         edit_uint64_with_commas(mr.VolBytes, ec7),
821         edit_uint64_with_suffix(mr.VolBytes, ec8),
822         jcr->JobErrors,
823         jcr->SDErrors,
824         fd_term_msg,
825         sd_term_msg,
826         term_msg);
827
828    Dmsg0(100, "Leave backup_cleanup()\n");
829 }
830
831 void update_bootstrap_file(JCR *jcr)
832 {
833    /* Now update the bootstrap file if any */
834    if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes &&
835        jcr->job->WriteBootstrap) {
836       FILE *fd;
837       BPIPE *bpipe = NULL;
838       int got_pipe = 0;
839       POOLMEM *fname = get_pool_memory(PM_FNAME);
840       fname = edit_job_codes(jcr, fname, jcr->job->WriteBootstrap, "");
841
842       VOL_PARAMS *VolParams = NULL;
843       int VolCount;
844       char edt[50], ed1[50], ed2[50];
845
846       if (*fname == '|') {
847          got_pipe = 1;
848          bpipe = open_bpipe(fname+1, 0, "w"); /* skip first char "|" */
849          fd = bpipe ? bpipe->wfd : NULL;
850       } else {
851          /* ***FIXME*** handle BASE */
852          fd = fopen(fname, jcr->is_JobLevel(L_FULL)?"w+b":"a+b");
853       }
854       if (fd) {
855          VolCount = db_get_job_volume_parameters(jcr, jcr->db, jcr->JobId,
856                     &VolParams);
857          if (VolCount == 0) {
858             Jmsg(jcr, M_ERROR, 0, _("Could not get Job Volume Parameters to "
859                  "update Bootstrap file. ERR=%s\n"), db_strerror(jcr->db));
860              if (jcr->SDJobFiles != 0) {
861                 jcr->setJobStatus(JS_ErrorTerminated);
862              }
863
864          }
865          /* Start output with when and who wrote it */
866          bstrftimes(edt, sizeof(edt), time(NULL));
867          fprintf(fd, "# %s - %s - %s%s\n", edt, jcr->jr.Job,
868                  level_to_str(jcr->getJobLevel()), jcr->since);
869          for (int i=0; i < VolCount; i++) {
870             /* Write the record */
871             fprintf(fd, "Volume=\"%s\"\n", VolParams[i].VolumeName);
872             fprintf(fd, "MediaType=\"%s\"\n", VolParams[i].MediaType);
873             if (VolParams[i].Slot > 0) {
874                fprintf(fd, "Slot=%d\n", VolParams[i].Slot);
875             }
876             fprintf(fd, "VolSessionId=%u\n", jcr->VolSessionId);
877             fprintf(fd, "VolSessionTime=%u\n", jcr->VolSessionTime);
878             fprintf(fd, "VolAddr=%s-%s\n", 
879                     edit_uint64(VolParams[i].StartAddr, ed1),
880                     edit_uint64(VolParams[i].EndAddr, ed2));
881             fprintf(fd, "FileIndex=%d-%d\n", VolParams[i].FirstIndex,
882                          VolParams[i].LastIndex);
883          }
884          if (VolParams) {
885             free(VolParams);
886          }
887          if (got_pipe) {
888             close_bpipe(bpipe);
889          } else {
890             fclose(fd);
891          }
892       } else {
893          berrno be;
894          Jmsg(jcr, M_ERROR, 0, _("Could not open WriteBootstrap file:\n"
895               "%s: ERR=%s\n"), fname, be.bstrerror());
896          jcr->setJobStatus(JS_ErrorTerminated);
897       }
898       free_pool_memory(fname);
899    }
900 }