]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/vbackup.c
Add a warning to Vbackup if accurate not set
[bacula/bacula] / bacula / src / dird / vbackup.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2008-2008 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *
30  *   Bacula Director -- vbackup.c -- responsible for doing virtual
31  *     backup jobs or in other words, consolidation or synthetic
32  *     backups.
33  *
34  *     Kern Sibbald, July MMVIII
35  *
36  *  Basic tasks done here:
37  *     Open DB and create records for this job.
38  *     Figure out what Jobs to copy.
39  *     Open Message Channel with Storage daemon to tell him a job will be starting.
40  *     Open connection with File daemon and pass him commands
41  *       to do the backup.
42  *     When the File daemon finishes the job, update the DB.
43  *
44  *   Version $Id: $
45  */
46
47 #include "bacula.h"
48 #include "dird.h"
49 #include "ua.h"
50
51 static const int dbglevel = 10;
52
53 static char OKbootstrap[] = "3000 OK bootstrap\n";
54
55 static bool create_bootstrap_file(JCR *jcr, POOLMEM *jobids);
56 void vbackup_cleanup(JCR *jcr, int TermCode);
57
58 /* 
59  * Called here before the job is run to do the job
60  *   specific setup.
61  */
62 bool do_vbackup_init(JCR *jcr)
63 {
64    char *p;
65
66    if (!get_or_create_fileset_record(jcr)) {
67       Dmsg1(dbglevel, "JobId=%d no FileSet\n", (int)jcr->JobId);
68       return false;
69    }
70
71    apply_pool_overrides(jcr);
72
73    if (!allow_duplicate_job(jcr)) {
74       return false;
75    }
76
77    jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->name());
78    if (jcr->jr.PoolId == 0) {
79       Dmsg1(dbglevel, "JobId=%d no PoolId\n", (int)jcr->JobId);
80       Jmsg(jcr, M_FATAL, 0, _("Could not get or create a Pool record.\n"));
81       return false;
82    }
83    /*
84     * Note, at this point, pool is the pool for this job.  We
85     *  transfer it to rpool (read pool), and a bit later,
86     *  pool will be changed to point to the write pool, 
87     *  which comes from pool->NextPool.
88     */
89    jcr->rpool = jcr->pool;            /* save read pool */
90    pm_strcpy(jcr->rpool_source, jcr->pool_source);
91
92    /* If pool storage specified, use it for restore */
93    copy_rstorage(jcr, jcr->pool->storage, _("Pool resource"));
94
95    Dmsg2(dbglevel, "Read pool=%s (From %s)\n", jcr->rpool->name(), jcr->rpool_source);
96
97    jcr->start_time = time(NULL);
98    jcr->jr.StartTime = jcr->start_time;
99    jcr->jr.JobLevel = L_FULL;      /* we want this to appear as a Full backup */
100    if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
101       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
102    }
103
104    POOLMEM *jobids = get_pool_memory(PM_FNAME);
105    jcr->jr.JobLevel = L_VIRTUAL_FULL;
106    db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, jobids);
107    jcr->jr.JobLevel = L_FULL;
108    Dmsg1(10, "Accurate jobids=%s\n", jobids);
109    if (*jobids == 0) {
110       free_pool_memory(jobids);
111       Jmsg(jcr, M_FATAL, 0, _("Cannot find previous JobIds.\n"));
112       return false;
113    }
114
115    /*
116     * Now we find the last job that ran and store it's info in
117     *  the previous_jr record.  We will set our times to the
118     *  values from that job so that anything changed after that
119     *  time will be picked up on the next backup.
120     */
121    p = strrchr(jobids, ',');              /* find last jobid */
122    if (p != NULL) {
123       p++;
124    } else {
125       p = jobids;
126    }
127    memset(&jcr->previous_jr, 0, sizeof(jcr->previous_jr));
128    jcr->previous_jr.JobId = str_to_int64(p);
129    if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
130       Jmsg(jcr, M_FATAL, 0, _("Error getting Job record for previous Job: ERR=%s"),
131                db_strerror(jcr->db));
132       return false;
133    }
134
135    if (!create_bootstrap_file(jcr, jobids)) {
136       Jmsg(jcr, M_FATAL, 0, _("Could not get or create the FileSet record.\n"));
137       free_pool_memory(jobids);
138       return false;
139    }
140    free_pool_memory(jobids);
141
142    /*
143     * If the original backup pool has a NextPool, make sure a 
144     *  record exists in the database. Note, in this case, we
145     *  will be backing up from pool to pool->NextPool.
146     */
147    if (jcr->pool->NextPool) {
148       jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->NextPool->name());
149       if (jcr->jr.PoolId == 0) {
150          return false;
151       }
152    }
153    if (!set_migration_wstorage(jcr, jcr->pool)) {
154       return false;
155    }
156    pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
157
158    Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
159
160 // create_clones(jcr);
161
162    return true;
163 }
164
165 /*
166  * Do a virtual backup, which consolidates all previous backups into
167  *  a sort of synthetic Full.
168  *
169  *  Returns:  false on failure
170  *            true  on success
171  */
172 bool do_vbackup(JCR *jcr)
173 {
174    char ed1[100];
175    BSOCK *sd;
176
177    Dmsg2(100, "rstorage=%p wstorage=%p\n", jcr->rstorage, jcr->wstorage);
178    Dmsg2(100, "Read store=%s, write store=%s\n", 
179       ((STORE *)jcr->rstorage->first())->name(),
180       ((STORE *)jcr->wstorage->first())->name());
181    /* ***FIXME***  we really should simply verify that the pools are different */
182    if (((STORE *)jcr->rstorage->first())->name() == ((STORE *)jcr->wstorage->first())->name()) {
183       Jmsg(jcr, M_FATAL, 0, _("Read storage \"%s\" same as write storage.\n"),
184            ((STORE *)jcr->rstorage->first())->name());
185       return false;
186    }
187
188    /* Print Job Start message */
189    Jmsg(jcr, M_INFO, 0, _("Start Virtual Backup JobId %s, Job=%s\n"),
190         edit_uint64(jcr->JobId, ed1), jcr->Job);
191    if (!jcr->accurate) {
192       Jmsg(jcr, M_WARNING, 0, 
193 _("This Job is not an Accurate backup so is not equivalent to a Full backup.\n"));
194    }
195
196    /*
197     * Open a message channel connection with the Storage
198     * daemon. This is to let him know that our client
199     * will be contacting him for a backup  session.
200     *
201     */
202    Dmsg0(110, "Open connection with storage daemon\n");
203    set_jcr_job_status(jcr, JS_WaitSD);
204    /*
205     * Start conversation with Storage daemon
206     */
207    if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
208       return false;
209    }
210    sd = jcr->store_bsock;
211
212    /*
213     * Now start a job with the Storage daemon
214     */
215    if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage)) {
216       return false;
217    }
218    Dmsg0(100, "Storage daemon connection OK\n");
219
220    if (!send_bootstrap_file(jcr, sd) ||
221        !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
222       return false;
223    }
224
225    /*    
226     * We re-update the job start record so that the start
227     *  time is set after the run before job.  This avoids 
228     *  that any files created by the run before job will
229     *  be saved twice.  They will be backed up in the current
230     *  job, but not in the next one unless they are changed.
231     *  Without this, they will be backed up in this job and
232     *  in the next job run because in that case, their date 
233     *   is after the start of this run.
234     */
235    jcr->start_time = time(NULL);
236    jcr->jr.StartTime = jcr->start_time;
237    jcr->jr.JobTDate = jcr->start_time;
238    set_jcr_job_status(jcr, JS_Running);
239
240    /* Update job start record */
241    if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
242       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
243       return false;
244    }
245
246    /*
247     * Start the job prior to starting the message thread below
248     * to avoid two threads from using the BSOCK structure at
249     * the same time.
250     */
251    if (!sd->fsend("run")) {
252       return false;
253    }
254
255    /*
256     * Now start a Storage daemon message thread
257     */
258    if (!start_storage_daemon_message_thread(jcr)) {
259       return false;
260    }
261
262    set_jcr_job_status(jcr, JS_Running);
263
264    /* Pickup Job termination data */
265    /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
266    wait_for_storage_daemon_termination(jcr);
267    set_jcr_job_status(jcr, jcr->SDJobStatus);
268    db_write_batch_file_records(jcr);    /* used by bulk batch file insert */
269    if (jcr->JobStatus != JS_Terminated) {
270       return false;
271    }
272
273    vbackup_cleanup(jcr, jcr->JobStatus);
274    return true;
275 }
276
277
278 /*
279  * Release resources allocated during backup.
280  */
281 void vbackup_cleanup(JCR *jcr, int TermCode)
282 {
283    char sdt[50], edt[50], schedt[50];
284    char ec1[30], ec3[30], ec4[30], compress[50];
285    char ec7[30], ec8[30], elapsed[50];
286    char term_code[100], sd_term_msg[100];
287    const char *term_msg;
288    int msg_type = M_INFO;
289    MEDIA_DBR mr;
290    CLIENT_DBR cr;
291    double kbps, compression;
292    utime_t RunTime;
293    POOL_MEM query(PM_MESSAGE);
294
295    Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
296    memset(&mr, 0, sizeof(mr));
297    memset(&cr, 0, sizeof(cr));
298
299    jcr->set_JobLevel(L_FULL);         /* we want this to appear as a Full backup */
300    jcr->jr.JobLevel = L_FULL;         /* we want this to appear as a Full backup */
301    jcr->JobFiles = jcr->SDJobFiles;
302    jcr->JobBytes = jcr->SDJobBytes;
303    update_job_end(jcr, TermCode);
304
305    /* Update final items to set them to the previous job's values */
306    Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
307                "JobTDate=%s WHERE JobId=%s", 
308       jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime, 
309       edit_uint64(jcr->previous_jr.JobTDate, ec1),
310       edit_uint64(jcr->JobId, ec3));
311    db_sql_query(jcr->db, query.c_str(), NULL, NULL);
312
313    if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
314       Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
315          db_strerror(jcr->db));
316       set_jcr_job_status(jcr, JS_ErrorTerminated);
317    }
318
319    bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
320    if (!db_get_client_record(jcr, jcr->db, &cr)) {
321       Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
322          db_strerror(jcr->db));
323    }
324
325    bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
326    if (!db_get_media_record(jcr, jcr->db, &mr)) {
327       Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
328          mr.VolumeName, db_strerror(jcr->db));
329       set_jcr_job_status(jcr, JS_ErrorTerminated);
330    }
331
332    update_bootstrap_file(jcr);
333
334    switch (jcr->JobStatus) {
335       case JS_Terminated:
336          if (jcr->Errors || jcr->SDErrors) {
337             term_msg = _("Backup OK -- with warnings");
338          } else {
339             term_msg = _("Backup OK");
340          }
341          break;
342       case JS_FatalError:
343       case JS_ErrorTerminated:
344          term_msg = _("*** Backup Error ***");
345          msg_type = M_ERROR;          /* Generate error message */
346          if (jcr->store_bsock) {
347             jcr->store_bsock->signal(BNET_TERMINATE);
348             if (jcr->SD_msg_chan) {
349                pthread_cancel(jcr->SD_msg_chan);
350             }
351          }
352          break;
353       case JS_Canceled:
354          term_msg = _("Backup Canceled");
355          if (jcr->store_bsock) {
356             jcr->store_bsock->signal(BNET_TERMINATE);
357             if (jcr->SD_msg_chan) {
358                pthread_cancel(jcr->SD_msg_chan);
359             }
360          }
361          break;
362       default:
363          term_msg = term_code;
364          sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
365          break;
366    }
367    bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
368    bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
369    bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
370    RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
371    if (RunTime <= 0) {
372       kbps = 0;
373    } else {
374       kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
375    }
376    if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
377       /*
378        * Note, if the job has erred, most likely it did not write any
379        *  tape, so suppress this "error" message since in that case
380        *  it is normal.  Or look at it the other way, only for a
381        *  normal exit should we complain about this error.
382        */
383       if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
384          Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
385       }
386       jcr->VolumeName[0] = 0;         /* none */
387    }
388
389    if (jcr->ReadBytes == 0) {
390       bstrncpy(compress, "None", sizeof(compress));
391    } else {
392       compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
393       if (compression < 0.5) {
394          bstrncpy(compress, "None", sizeof(compress));
395       } else {
396          bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
397       }
398    }
399    jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
400
401    Jmsg(jcr, msg_type, 0, _("Bacula %s %s (%s): %s\n"
402 "  Build OS:               %s %s %s\n"
403 "  JobId:                  %d\n"
404 "  Job:                    %s\n"
405 "  Backup Level:           Virtual Full\n"
406 "  Client:                 \"%s\" %s\n"
407 "  FileSet:                \"%s\" %s\n"
408 "  Pool:                   \"%s\" (From %s)\n"
409 "  Catalog:                \"%s\" (From %s)\n"
410 "  Storage:                \"%s\" (From %s)\n"
411 "  Scheduled time:         %s\n"
412 "  Start time:             %s\n"
413 "  End time:               %s\n"
414 "  Elapsed time:           %s\n"
415 "  Priority:               %d\n"
416 "  SD Files Written:       %s\n"
417 "  SD Bytes Written:       %s (%sB)\n"
418 "  Rate:                   %.1f KB/s\n"
419 "  Encryption:             %s\n"
420 "  Accurate:               %s\n"
421 "  Volume name(s):         %s\n"
422 "  Volume Session Id:      %d\n"
423 "  Volume Session Time:    %d\n"
424 "  Last Volume Bytes:      %s (%sB)\n"
425 "  SD Errors:              %d\n"
426 "  SD termination status:  %s\n"
427 "  Termination:            %s\n\n"),
428         my_name, VERSION, LSMDATE, edt,
429         HOST_OS, DISTNAME, DISTVER,
430         jcr->jr.JobId,
431         jcr->jr.Job,
432         jcr->client->name(), cr.Uname,
433         jcr->fileset->name(), jcr->FSCreateTime,
434         jcr->pool->name(), jcr->pool_source,
435         jcr->catalog->name(), jcr->catalog_source,
436         jcr->wstore->name(), jcr->wstore_source,
437         schedt,
438         sdt,
439         edt,
440         edit_utime(RunTime, elapsed, sizeof(elapsed)),
441         jcr->JobPriority,
442         edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
443         edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
444         edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
445         kbps,
446         jcr->Encrypt?_("yes"):_("no"),
447         jcr->accurate?_("yes"):_("no"),
448         jcr->VolumeName,
449         jcr->VolSessionId,
450         jcr->VolSessionTime,
451         edit_uint64_with_commas(mr.VolBytes, ec7),
452         edit_uint64_with_suffix(mr.VolBytes, ec8),
453         jcr->SDErrors,
454         sd_term_msg,
455         term_msg);
456
457    Dmsg0(100, "Leave vbackup_cleanup()\n");
458 }
459
460 /*
461  * This callback routine is responsible for inserting the
462  *  items it gets into the bootstrap structure. For each JobId selected
463  *  this routine is called once for each file. We do not allow
464  *  duplicate filenames, but instead keep the info from the most
465  *  recent file entered (i.e. the JobIds are assumed to be sorted)
466  *
467  *   See uar_sel_files in sql_cmds.c for query that calls us.
468  *      row[0]=Path, row[1]=Filename, row[2]=FileIndex
469  *      row[3]=JobId row[4]=LStat
470  */
471 int insert_bootstrap_handler(void *ctx, int num_fields, char **row)
472 {
473    JobId_t JobId;
474    int FileIndex;
475    RBSR *bsr = (RBSR *)ctx;
476
477    JobId = str_to_int64(row[3]);
478    FileIndex = str_to_int64(row[2]);
479    add_findex(bsr, JobId, FileIndex);
480    return 0;
481 }
482
483
484 static bool create_bootstrap_file(JCR *jcr, POOLMEM *jobids)
485 {
486    RESTORE_CTX rx;
487    UAContext *ua;
488
489    memset(&rx, 0, sizeof(rx));
490    rx.bsr = new_bsr();
491    ua = new_ua_context(jcr);
492    rx.JobIds = jobids;
493
494 #define new_get_file_list
495 #ifdef new_get_file_list
496    if (!db_get_file_list(jcr, ua->db, jobids, insert_bootstrap_handler, (void *)rx.bsr)) {
497       Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
498    }
499 #else
500    char *p;
501    JobId_t JobId, last_JobId = 0;
502    rx.query = get_pool_memory(PM_MESSAGE);
503    for (p=rx.JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
504       char ed1[50];
505
506       if (JobId == last_JobId) {
507          continue;                    /* eliminate duplicate JobIds */
508       }
509       last_JobId = JobId;
510       /*
511        * Find files for this JobId and insert them in the tree
512        */
513       Mmsg(rx.query, uar_sel_files, edit_int64(JobId, ed1));
514       Dmsg1(100, "uar_sel_files=%s\n", rx.query);
515       if (!db_sql_query(ua->db, rx.query, insert_bootstrap_handler, (void *)rx.bsr)) {
516          Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
517       }
518       free_pool_memory(rx.query);
519       rx.query = NULL;
520    }
521 #endif
522
523    complete_bsr(ua, rx.bsr);
524    jcr->ExpectedFiles = write_bsr_file(ua, rx);
525    if (debug_level >= 10) {
526       Dmsg1(000,  "Found %d files to consolidate.\n", jcr->ExpectedFiles);
527    }
528    if (jcr->ExpectedFiles == 0) {
529       free_ua_context(ua);
530       free_bsr(rx.bsr);
531       return false;
532    }
533    free_ua_context(ua);
534    free_bsr(rx.bsr);
535    return true;
536 }