2 Bacula® - The Network Backup Solution
4 Copyright (C) 2008-2009 Free Software Foundation Europe e.V.
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
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.
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
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.
30 * Bacula Director -- vbackup.c -- responsible for doing virtual
31 * backup jobs or in other words, consolidation or synthetic
34 * Kern Sibbald, July MMVIII
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
42 * When the File daemon finishes the job, update the DB.
51 static const int dbglevel = 10;
53 static bool create_bootstrap_file(JCR *jcr, POOLMEM *jobids);
54 void vbackup_cleanup(JCR *jcr, int TermCode);
57 * Called here before the job is run to do the job
60 bool do_vbackup_init(JCR *jcr)
63 if (!get_or_create_fileset_record(jcr)) {
64 Dmsg1(dbglevel, "JobId=%d no FileSet\n", (int)jcr->JobId);
68 apply_pool_overrides(jcr);
70 if (!allow_duplicate_job(jcr)) {
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"));
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.
86 jcr->rpool = jcr->pool; /* save read pool */
87 pm_strcpy(jcr->rpool_source, jcr->pool_source);
89 /* If pool storage specified, use it for restore */
90 copy_rstorage(jcr, jcr->pool->storage, _("Pool resource"));
92 Dmsg2(dbglevel, "Read pool=%s (From %s)\n", jcr->rpool->name(), jcr->rpool_source);
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));
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.
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) {
113 if (!set_migration_wstorage(jcr, jcr->pool)) {
116 jcr->pool = jcr->pool->NextPool;
117 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
119 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
121 // create_clones(jcr);
127 * Do a virtual backup, which consolidates all previous backups into
128 * a sort of synthetic Full.
130 * Returns: false on failure
133 bool do_vbackup(JCR *jcr)
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 */
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());
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"));
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);
166 free_pool_memory(jobids);
167 Jmsg(jcr, M_FATAL, 0, _("No previous Jobs found.\n"));
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.
177 p = strrchr(jobids, ','); /* find last jobid */
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));
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);
197 free_pool_memory(jobids);
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.
205 Dmsg0(110, "Open connection with storage daemon\n");
206 set_jcr_job_status(jcr, JS_WaitSD);
208 * Start conversation with Storage daemon
210 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
213 sd = jcr->store_bsock;
216 * Now start a job with the Storage daemon
218 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage, /*send_bsr*/true)) {
221 Dmsg0(100, "Storage daemon connection OK\n");
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.
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);
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));
245 * Start the job prior to starting the message thread below
246 * to avoid two threads from using the BSOCK structure at
249 if (!sd->fsend("run")) {
254 * Now start a Storage daemon message thread
256 if (!start_storage_daemon_message_thread(jcr)) {
260 set_jcr_job_status(jcr, JS_Running);
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) {
271 vbackup_cleanup(jcr, jcr->JobStatus);
277 * Release resources allocated during backup.
279 void vbackup_cleanup(JCR *jcr, int TermCode)
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;
289 double kbps, compression;
291 POOL_MEM query(PM_MESSAGE);
293 Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
294 memset(&mr, 0, sizeof(mr));
295 memset(&cr, 0, sizeof(cr));
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);
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);
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);
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));
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);
331 update_bootstrap_file(jcr);
333 switch (jcr->JobStatus) {
335 if (jcr->JobErrors || jcr->SDErrors) {
336 term_msg = _("Backup OK -- with warnings");
338 term_msg = _("Backup OK");
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);
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);
362 term_msg = term_code;
363 sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
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;
373 kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
375 if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
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.
382 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
383 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
385 jcr->VolumeName[0] = 0; /* none */
388 if (jcr->ReadBytes == 0) {
389 bstrncpy(compress, "None", sizeof(compress));
391 compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
392 if (compression < 0.5) {
393 bstrncpy(compress, "None", sizeof(compress));
395 bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
398 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
400 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
401 " Build OS: %s %s %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"
413 " Elapsed time: %s\n"
415 " SD Files Written: %s\n"
416 " SD Bytes Written: %s (%sB)\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"
423 " SD termination status: %s\n"
424 " Termination: %s\n\n"),
425 BACULA, my_name, VERSION, LSMDATE, edt,
426 HOST_OS, DISTNAME, DISTVER,
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,
437 edit_utime(RunTime, elapsed, sizeof(elapsed)),
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),
446 edit_uint64_with_commas(mr.VolBytes, ec7),
447 edit_uint64_with_suffix(mr.VolBytes, ec8),
452 Dmsg0(100, "Leave vbackup_cleanup()\n");
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)
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
466 int insert_bootstrap_handler(void *ctx, int num_fields, char **row)
470 RBSR *bsr = (RBSR *)ctx;
472 JobId = str_to_int64(row[3]);
473 FileIndex = str_to_int64(row[2]);
474 add_findex(bsr, JobId, FileIndex);
479 static bool create_bootstrap_file(JCR *jcr, POOLMEM *jobids)
484 memset(&rx, 0, sizeof(rx));
486 ua = new_ua_context(jcr);
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));
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; ) {
501 if (JobId == last_JobId) {
502 continue; /* eliminate duplicate JobIds */
506 * Find files for this JobId and insert them in the tree
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));
513 free_pool_memory(rx.query);
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);
523 if (jcr->ExpectedFiles == 0) {