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