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 char OKbootstrap[] = "3000 OK bootstrap\n";
55 static bool create_bootstrap_file(JCR *jcr, POOLMEM *jobids);
56 void vbackup_cleanup(JCR *jcr, int TermCode);
59 * Called here before the job is run to do the job
62 bool do_vbackup_init(JCR *jcr)
65 if (!get_or_create_fileset_record(jcr)) {
66 Dmsg1(dbglevel, "JobId=%d no FileSet\n", (int)jcr->JobId);
70 apply_pool_overrides(jcr);
72 if (!allow_duplicate_job(jcr)) {
76 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->name());
77 if (jcr->jr.PoolId == 0) {
78 Dmsg1(dbglevel, "JobId=%d no PoolId\n", (int)jcr->JobId);
79 Jmsg(jcr, M_FATAL, 0, _("Could not get or create a Pool record.\n"));
83 * Note, at this point, pool is the pool for this job. We
84 * transfer it to rpool (read pool), and a bit later,
85 * pool will be changed to point to the write pool,
86 * which comes from pool->NextPool.
88 jcr->rpool = jcr->pool; /* save read pool */
89 pm_strcpy(jcr->rpool_source, jcr->pool_source);
91 /* If pool storage specified, use it for restore */
92 copy_rstorage(jcr, jcr->pool->storage, _("Pool resource"));
94 Dmsg2(dbglevel, "Read pool=%s (From %s)\n", jcr->rpool->name(), jcr->rpool_source);
96 jcr->start_time = time(NULL);
97 jcr->jr.StartTime = jcr->start_time;
98 jcr->jr.JobLevel = L_FULL; /* we want this to appear as a Full backup */
99 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
100 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
105 * If the original backup pool has a NextPool, make sure a
106 * record exists in the database. Note, in this case, we
107 * will be backing up from pool to pool->NextPool.
109 if (jcr->pool->NextPool) {
110 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->NextPool->name());
111 if (jcr->jr.PoolId == 0) {
115 if (!set_migration_wstorage(jcr, jcr->pool)) {
118 jcr->pool = jcr->pool->NextPool;
119 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
121 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
123 // create_clones(jcr);
129 * Do a virtual backup, which consolidates all previous backups into
130 * a sort of synthetic Full.
132 * Returns: false on failure
135 bool do_vbackup(JCR *jcr)
141 Dmsg2(100, "rstorage=%p wstorage=%p\n", jcr->rstorage, jcr->wstorage);
142 Dmsg2(100, "Read store=%s, write store=%s\n",
143 ((STORE *)jcr->rstorage->first())->name(),
144 ((STORE *)jcr->wstorage->first())->name());
145 /* ***FIXME*** we really should simply verify that the pools are different */
147 if (((STORE *)jcr->rstorage->first())->name() == ((STORE *)jcr->wstorage->first())->name()) {
148 Jmsg(jcr, M_FATAL, 0, _("Read storage \"%s\" same as write storage.\n"),
149 ((STORE *)jcr->rstorage->first())->name());
154 /* Print Job Start message */
155 Jmsg(jcr, M_INFO, 0, _("Start Virtual Backup JobId %s, Job=%s\n"),
156 edit_uint64(jcr->JobId, ed1), jcr->Job);
157 if (!jcr->accurate) {
158 Jmsg(jcr, M_WARNING, 0,
159 _("This Job is not an Accurate backup so is not equivalent to a Full backup.\n"));
162 POOLMEM *jobids = get_pool_memory(PM_FNAME);
163 jcr->jr.JobLevel = L_VIRTUAL_FULL;
164 db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, jobids);
165 jcr->jr.JobLevel = L_FULL;
166 Dmsg1(10, "Accurate jobids=%s\n", jobids);
168 free_pool_memory(jobids);
169 Jmsg(jcr, M_FATAL, 0, _("No previous Jobs found.\n"));
174 * Now we find the last job that ran and store it's info in
175 * the previous_jr record. We will set our times to the
176 * values from that job so that anything changed after that
177 * time will be picked up on the next backup.
179 p = strrchr(jobids, ','); /* find last jobid */
185 memset(&jcr->previous_jr, 0, sizeof(jcr->previous_jr));
186 jcr->previous_jr.JobId = str_to_int64(p);
187 Dmsg1(10, "Previous JobId=%s\n", p);
188 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
189 Jmsg(jcr, M_FATAL, 0, _("Error getting Job record for previous Job: ERR=%s"),
190 db_strerror(jcr->db));
194 if (!create_bootstrap_file(jcr, jobids)) {
195 Jmsg(jcr, M_FATAL, 0, _("Could not get or create the FileSet record.\n"));
196 free_pool_memory(jobids);
199 free_pool_memory(jobids);
202 * Open a message channel connection with the Storage
203 * daemon. This is to let him know that our client
204 * will be contacting him for a backup session.
207 Dmsg0(110, "Open connection with storage daemon\n");
208 set_jcr_job_status(jcr, JS_WaitSD);
210 * Start conversation with Storage daemon
212 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
215 sd = jcr->store_bsock;
218 * Now start a job with the Storage daemon
220 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage)) {
223 Dmsg0(100, "Storage daemon connection OK\n");
225 if (!send_bootstrap_file(jcr, sd) ||
226 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
231 * We re-update the job start record so that the start
232 * time is set after the run before job. This avoids
233 * that any files created by the run before job will
234 * be saved twice. They will be backed up in the current
235 * job, but not in the next one unless they are changed.
236 * Without this, they will be backed up in this job and
237 * in the next job run because in that case, their date
238 * is after the start of this run.
240 jcr->start_time = time(NULL);
241 jcr->jr.StartTime = jcr->start_time;
242 jcr->jr.JobTDate = jcr->start_time;
243 set_jcr_job_status(jcr, JS_Running);
245 /* Update job start record */
246 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
247 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
252 * Start the job prior to starting the message thread below
253 * to avoid two threads from using the BSOCK structure at
256 if (!sd->fsend("run")) {
261 * Now start a Storage daemon message thread
263 if (!start_storage_daemon_message_thread(jcr)) {
267 set_jcr_job_status(jcr, JS_Running);
269 /* Pickup Job termination data */
270 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
271 wait_for_storage_daemon_termination(jcr);
272 set_jcr_job_status(jcr, jcr->SDJobStatus);
273 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
274 if (jcr->JobStatus != JS_Terminated) {
278 vbackup_cleanup(jcr, jcr->JobStatus);
284 * Release resources allocated during backup.
286 void vbackup_cleanup(JCR *jcr, int TermCode)
288 char sdt[50], edt[50], schedt[50];
289 char ec1[30], ec3[30], ec4[30], compress[50];
290 char ec7[30], ec8[30], elapsed[50];
291 char term_code[100], sd_term_msg[100];
292 const char *term_msg;
293 int msg_type = M_INFO;
296 double kbps, compression;
298 POOL_MEM query(PM_MESSAGE);
300 Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
301 memset(&mr, 0, sizeof(mr));
302 memset(&cr, 0, sizeof(cr));
304 jcr->set_JobLevel(L_FULL); /* we want this to appear as a Full backup */
305 jcr->jr.JobLevel = L_FULL; /* we want this to appear as a Full backup */
306 jcr->JobFiles = jcr->SDJobFiles;
307 jcr->JobBytes = jcr->SDJobBytes;
308 update_job_end(jcr, TermCode);
310 /* Update final items to set them to the previous job's values */
311 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
312 "JobTDate=%s WHERE JobId=%s",
313 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
314 edit_uint64(jcr->previous_jr.JobTDate, ec1),
315 edit_uint64(jcr->JobId, ec3));
316 db_sql_query(jcr->db, query.c_str(), NULL, NULL);
318 /* Get the fully updated job record */
319 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
320 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
321 db_strerror(jcr->db));
322 set_jcr_job_status(jcr, JS_ErrorTerminated);
325 bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
326 if (!db_get_client_record(jcr, jcr->db, &cr)) {
327 Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
328 db_strerror(jcr->db));
331 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
332 if (!db_get_media_record(jcr, jcr->db, &mr)) {
333 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
334 mr.VolumeName, db_strerror(jcr->db));
335 set_jcr_job_status(jcr, JS_ErrorTerminated);
338 update_bootstrap_file(jcr);
340 switch (jcr->JobStatus) {
342 if (jcr->JobErrors || jcr->SDErrors) {
343 term_msg = _("Backup OK -- with warnings");
345 term_msg = _("Backup OK");
349 case JS_ErrorTerminated:
350 term_msg = _("*** Backup Error ***");
351 msg_type = M_ERROR; /* Generate error message */
352 if (jcr->store_bsock) {
353 jcr->store_bsock->signal(BNET_TERMINATE);
354 if (jcr->SD_msg_chan) {
355 pthread_cancel(jcr->SD_msg_chan);
360 term_msg = _("Backup Canceled");
361 if (jcr->store_bsock) {
362 jcr->store_bsock->signal(BNET_TERMINATE);
363 if (jcr->SD_msg_chan) {
364 pthread_cancel(jcr->SD_msg_chan);
369 term_msg = term_code;
370 sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
373 bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
374 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
375 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
376 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
380 kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
382 if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
384 * Note, if the job has erred, most likely it did not write any
385 * tape, so suppress this "error" message since in that case
386 * it is normal. Or look at it the other way, only for a
387 * normal exit should we complain about this error.
389 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
390 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
392 jcr->VolumeName[0] = 0; /* none */
395 if (jcr->ReadBytes == 0) {
396 bstrncpy(compress, "None", sizeof(compress));
398 compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
399 if (compression < 0.5) {
400 bstrncpy(compress, "None", sizeof(compress));
402 bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
405 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
407 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
408 " Build OS: %s %s %s\n"
411 " Backup Level: Virtual Full\n"
412 " Client: \"%s\" %s\n"
413 " FileSet: \"%s\" %s\n"
414 " Pool: \"%s\" (From %s)\n"
415 " Catalog: \"%s\" (From %s)\n"
416 " Storage: \"%s\" (From %s)\n"
417 " Scheduled time: %s\n"
420 " Elapsed time: %s\n"
422 " SD Files Written: %s\n"
423 " SD Bytes Written: %s (%sB)\n"
425 " Volume name(s): %s\n"
426 " Volume Session Id: %d\n"
427 " Volume Session Time: %d\n"
428 " Last Volume Bytes: %s (%sB)\n"
430 " SD termination status: %s\n"
431 " Termination: %s\n\n"),
432 BACULA, my_name, VERSION, LSMDATE, edt,
433 HOST_OS, DISTNAME, DISTVER,
436 jcr->client->name(), cr.Uname,
437 jcr->fileset->name(), jcr->FSCreateTime,
438 jcr->pool->name(), jcr->pool_source,
439 jcr->catalog->name(), jcr->catalog_source,
440 jcr->wstore->name(), jcr->wstore_source,
444 edit_utime(RunTime, elapsed, sizeof(elapsed)),
446 edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
447 edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
448 edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
453 edit_uint64_with_commas(mr.VolBytes, ec7),
454 edit_uint64_with_suffix(mr.VolBytes, ec8),
459 Dmsg0(100, "Leave vbackup_cleanup()\n");
463 * This callback routine is responsible for inserting the
464 * items it gets into the bootstrap structure. For each JobId selected
465 * this routine is called once for each file. We do not allow
466 * duplicate filenames, but instead keep the info from the most
467 * recent file entered (i.e. the JobIds are assumed to be sorted)
469 * See uar_sel_files in sql_cmds.c for query that calls us.
470 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
471 * row[3]=JobId row[4]=LStat
473 int insert_bootstrap_handler(void *ctx, int num_fields, char **row)
477 RBSR *bsr = (RBSR *)ctx;
479 JobId = str_to_int64(row[3]);
480 FileIndex = str_to_int64(row[2]);
481 add_findex(bsr, JobId, FileIndex);
486 static bool create_bootstrap_file(JCR *jcr, POOLMEM *jobids)
491 memset(&rx, 0, sizeof(rx));
493 ua = new_ua_context(jcr);
496 #define new_get_file_list
497 #ifdef new_get_file_list
498 if (!db_get_file_list(jcr, ua->db, jobids, insert_bootstrap_handler, (void *)rx.bsr)) {
499 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
503 JobId_t JobId, last_JobId = 0;
504 rx.query = get_pool_memory(PM_MESSAGE);
505 for (p=rx.JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
508 if (JobId == last_JobId) {
509 continue; /* eliminate duplicate JobIds */
513 * Find files for this JobId and insert them in the tree
515 Mmsg(rx.query, uar_sel_files, edit_int64(JobId, ed1));
516 Dmsg1(100, "uar_sel_files=%s\n", rx.query);
517 if (!db_sql_query(ua->db, rx.query, insert_bootstrap_handler, (void *)rx.bsr)) {
518 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
520 free_pool_memory(rx.query);
525 complete_bsr(ua, rx.bsr);
526 jcr->ExpectedFiles = write_bsr_file(ua, rx);
527 if (debug_level >= 10) {
528 Dmsg1(000, "Found %d files to consolidate.\n", jcr->ExpectedFiles);
530 if (jcr->ExpectedFiles == 0) {