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