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