]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/vbackup.c
kes Fix problem of Virtual backup not writing a sequential FileIndex.
[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
192    /*
193     * Open a message channel connection with the Storage
194     * daemon. This is to let him know that our client
195     * will be contacting him for a backup  session.
196     *
197     */
198    Dmsg0(110, "Open connection with storage daemon\n");
199    set_jcr_job_status(jcr, JS_WaitSD);
200    /*
201     * Start conversation with Storage daemon
202     */
203    if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
204       return false;
205    }
206    sd = jcr->store_bsock;
207
208    /*
209     * Now start a job with the Storage daemon
210     */
211    if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage)) {
212       return false;
213    }
214    Dmsg0(100, "Storage daemon connection OK\n");
215
216    if (!send_bootstrap_file(jcr, sd) ||
217        !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
218       return false;
219    }
220
221    /*    
222     * We re-update the job start record so that the start
223     *  time is set after the run before job.  This avoids 
224     *  that any files created by the run before job will
225     *  be saved twice.  They will be backed up in the current
226     *  job, but not in the next one unless they are changed.
227     *  Without this, they will be backed up in this job and
228     *  in the next job run because in that case, their date 
229     *   is after the start of this run.
230     */
231    jcr->start_time = time(NULL);
232    jcr->jr.StartTime = jcr->start_time;
233    jcr->jr.JobTDate = jcr->start_time;
234    set_jcr_job_status(jcr, JS_Running);
235
236    /* Update job start record */
237    if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
238       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
239       return false;
240    }
241
242    /*
243     * Start the job prior to starting the message thread below
244     * to avoid two threads from using the BSOCK structure at
245     * the same time.
246     */
247    if (!sd->fsend("run")) {
248       return false;
249    }
250
251    /*
252     * Now start a Storage daemon message thread
253     */
254    if (!start_storage_daemon_message_thread(jcr)) {
255       return false;
256    }
257
258    set_jcr_job_status(jcr, JS_Running);
259
260    /* Pickup Job termination data */
261    /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
262    wait_for_storage_daemon_termination(jcr);
263    set_jcr_job_status(jcr, jcr->SDJobStatus);
264    db_write_batch_file_records(jcr);    /* used by bulk batch file insert */
265    if (jcr->JobStatus != JS_Terminated) {
266       return false;
267    }
268
269    vbackup_cleanup(jcr, jcr->JobStatus);
270    return true;
271 }
272
273
274 /*
275  * Release resources allocated during backup.
276  */
277 void vbackup_cleanup(JCR *jcr, int TermCode)
278 {
279    char sdt[50], edt[50], schedt[50];
280    char ec1[30], ec3[30], ec4[30], compress[50];
281    char ec7[30], ec8[30], elapsed[50];
282    char term_code[100], sd_term_msg[100];
283    const char *term_msg;
284    int msg_type = M_INFO;
285    MEDIA_DBR mr;
286    CLIENT_DBR cr;
287    double kbps, compression;
288    utime_t RunTime;
289    POOL_MEM query(PM_MESSAGE);
290
291    Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
292    memset(&mr, 0, sizeof(mr));
293    memset(&cr, 0, sizeof(cr));
294
295    jcr->set_JobLevel(L_FULL);         /* we want this to appear as a Full backup */
296    jcr->jr.JobLevel = L_FULL;         /* we want this to appear as a Full backup */
297    jcr->JobFiles = jcr->SDJobFiles;
298    jcr->JobBytes = jcr->SDJobBytes;
299    update_job_end(jcr, TermCode);
300
301    /* Update final items to set them to the previous job's values */
302    Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
303                "JobTDate=%s WHERE JobId=%s", 
304       jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime, 
305       edit_uint64(jcr->previous_jr.JobTDate, ec1),
306       edit_uint64(jcr->JobId, ec3));
307    db_sql_query(jcr->db, query.c_str(), NULL, NULL);
308
309    if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
310       Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
311          db_strerror(jcr->db));
312       set_jcr_job_status(jcr, JS_ErrorTerminated);
313    }
314
315    bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
316    if (!db_get_client_record(jcr, jcr->db, &cr)) {
317       Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
318          db_strerror(jcr->db));
319    }
320
321    bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
322    if (!db_get_media_record(jcr, jcr->db, &mr)) {
323       Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
324          mr.VolumeName, db_strerror(jcr->db));
325       set_jcr_job_status(jcr, JS_ErrorTerminated);
326    }
327
328    update_bootstrap_file(jcr);
329
330    switch (jcr->JobStatus) {
331       case JS_Terminated:
332          if (jcr->Errors || jcr->SDErrors) {
333             term_msg = _("Backup OK -- with warnings");
334          } else {
335             term_msg = _("Backup OK");
336          }
337          break;
338       case JS_FatalError:
339       case JS_ErrorTerminated:
340          term_msg = _("*** Backup Error ***");
341          msg_type = M_ERROR;          /* Generate error message */
342          if (jcr->store_bsock) {
343             jcr->store_bsock->signal(BNET_TERMINATE);
344             if (jcr->SD_msg_chan) {
345                pthread_cancel(jcr->SD_msg_chan);
346             }
347          }
348          break;
349       case JS_Canceled:
350          term_msg = _("Backup Canceled");
351          if (jcr->store_bsock) {
352             jcr->store_bsock->signal(BNET_TERMINATE);
353             if (jcr->SD_msg_chan) {
354                pthread_cancel(jcr->SD_msg_chan);
355             }
356          }
357          break;
358       default:
359          term_msg = term_code;
360          sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
361          break;
362    }
363    bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
364    bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
365    bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
366    RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
367    if (RunTime <= 0) {
368       kbps = 0;
369    } else {
370       kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
371    }
372    if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
373       /*
374        * Note, if the job has erred, most likely it did not write any
375        *  tape, so suppress this "error" message since in that case
376        *  it is normal.  Or look at it the other way, only for a
377        *  normal exit should we complain about this error.
378        */
379       if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
380          Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
381       }
382       jcr->VolumeName[0] = 0;         /* none */
383    }
384
385    if (jcr->ReadBytes == 0) {
386       bstrncpy(compress, "None", sizeof(compress));
387    } else {
388       compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
389       if (compression < 0.5) {
390          bstrncpy(compress, "None", sizeof(compress));
391       } else {
392          bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
393       }
394    }
395    jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
396
397    Jmsg(jcr, msg_type, 0, _("Bacula %s %s (%s): %s\n"
398 "  Build OS:               %s %s %s\n"
399 "  JobId:                  %d\n"
400 "  Job:                    %s\n"
401 "  Backup Level:           Virtual Full\n"
402 "  Client:                 \"%s\" %s\n"
403 "  FileSet:                \"%s\" %s\n"
404 "  Pool:                   \"%s\" (From %s)\n"
405 "  Catalog:                \"%s\" (From %s)\n"
406 "  Storage:                \"%s\" (From %s)\n"
407 "  Scheduled time:         %s\n"
408 "  Start time:             %s\n"
409 "  End time:               %s\n"
410 "  Elapsed time:           %s\n"
411 "  Priority:               %d\n"
412 "  SD Files Written:       %s\n"
413 "  SD Bytes Written:       %s (%sB)\n"
414 "  Rate:                   %.1f KB/s\n"
415 "  Encryption:             %s\n"
416 "  Accurate:               %s\n"
417 "  Volume name(s):         %s\n"
418 "  Volume Session Id:      %d\n"
419 "  Volume Session Time:    %d\n"
420 "  Last Volume Bytes:      %s (%sB)\n"
421 "  SD Errors:              %d\n"
422 "  SD termination status:  %s\n"
423 "  Termination:            %s\n\n"),
424         my_name, VERSION, LSMDATE, edt,
425         HOST_OS, DISTNAME, DISTVER,
426         jcr->jr.JobId,
427         jcr->jr.Job,
428         jcr->client->name(), cr.Uname,
429         jcr->fileset->name(), jcr->FSCreateTime,
430         jcr->pool->name(), jcr->pool_source,
431         jcr->catalog->name(), jcr->catalog_source,
432         jcr->wstore->name(), jcr->wstore_source,
433         schedt,
434         sdt,
435         edt,
436         edit_utime(RunTime, elapsed, sizeof(elapsed)),
437         jcr->JobPriority,
438         edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
439         edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
440         edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
441         kbps,
442         jcr->Encrypt?_("yes"):_("no"),
443         jcr->accurate?_("yes"):_("no"),
444         jcr->VolumeName,
445         jcr->VolSessionId,
446         jcr->VolSessionTime,
447         edit_uint64_with_commas(mr.VolBytes, ec7),
448         edit_uint64_with_suffix(mr.VolBytes, ec8),
449         jcr->SDErrors,
450         sd_term_msg,
451         term_msg);
452
453    Dmsg0(100, "Leave vbackup_cleanup()\n");
454 }
455
456 /*
457  * This callback routine is responsible for inserting the
458  *  items it gets into the bootstrap structure. For each JobId selected
459  *  this routine is called once for each file. We do not allow
460  *  duplicate filenames, but instead keep the info from the most
461  *  recent file entered (i.e. the JobIds are assumed to be sorted)
462  *
463  *   See uar_sel_files in sql_cmds.c for query that calls us.
464  *      row[0]=Path, row[1]=Filename, row[2]=FileIndex
465  *      row[3]=JobId row[4]=LStat
466  */
467 int insert_bootstrap_handler(void *ctx, int num_fields, char **row)
468 {
469    JobId_t JobId;
470    int FileIndex;
471    RBSR *bsr = (RBSR *)ctx;
472
473    JobId = str_to_int64(row[3]);
474    FileIndex = str_to_int64(row[2]);
475    add_findex(bsr, JobId, FileIndex);
476    return 0;
477 }
478
479
480 static bool create_bootstrap_file(JCR *jcr, POOLMEM *jobids)
481 {
482    RESTORE_CTX rx;
483    UAContext *ua;
484
485    memset(&rx, 0, sizeof(rx));
486    rx.bsr = new_bsr();
487    ua = new_ua_context(jcr);
488    rx.JobIds = jobids;
489
490 #define new_get_file_list
491 #ifdef new_get_file_list
492    if (!db_get_file_list(jcr, ua->db, jobids, insert_bootstrap_handler, (void *)rx.bsr)) {
493       Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
494    }
495 #else
496    char *p;
497    JobId_t JobId, last_JobId = 0;
498    rx.query = get_pool_memory(PM_MESSAGE);
499    for (p=rx.JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
500       char ed1[50];
501
502       if (JobId == last_JobId) {
503          continue;                    /* eliminate duplicate JobIds */
504       }
505       last_JobId = JobId;
506       /*
507        * Find files for this JobId and insert them in the tree
508        */
509       Mmsg(rx.query, uar_sel_files, edit_int64(JobId, ed1));
510       Dmsg1(100, "uar_sel_files=%s\n", rx.query);
511       if (!db_sql_query(ua->db, rx.query, insert_bootstrap_handler, (void *)rx.bsr)) {
512          Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
513       }
514       free_pool_memory(rx.query);
515       rx.query = NULL;
516    }
517 #endif
518
519    complete_bsr(ua, rx.bsr);
520    jcr->ExpectedFiles = write_bsr_file(ua, rx);
521    if (debug_level >= 10) {
522       Dmsg1(000,  "Found %d files to consolidate.\n", jcr->ExpectedFiles);
523    }
524    if (jcr->ExpectedFiles == 0) {
525       free_ua_context(ua);
526       free_bsr(rx.bsr);
527       return false;
528    }
529    free_ua_context(ua);
530    free_bsr(rx.bsr);
531    return true;
532 }