2 Bacula® - The Network Backup Solution
4 Copyright (C) 2008-2008 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)
66 if (!get_or_create_fileset_record(jcr)) {
67 Dmsg1(dbglevel, "JobId=%d no FileSet\n", (int)jcr->JobId);
71 apply_pool_overrides(jcr);
73 if (!allow_duplicate_job(jcr)) {
77 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->name());
78 if (jcr->jr.PoolId == 0) {
79 Dmsg1(dbglevel, "JobId=%d no PoolId\n", (int)jcr->JobId);
80 Jmsg(jcr, M_FATAL, 0, _("Could not get or create a Pool record.\n"));
84 * Note, at this point, pool is the pool for this job. We
85 * transfer it to rpool (read pool), and a bit later,
86 * pool will be changed to point to the write pool,
87 * which comes from pool->NextPool.
89 jcr->rpool = jcr->pool; /* save read pool */
90 pm_strcpy(jcr->rpool_source, jcr->pool_source);
92 /* If pool storage specified, use it for restore */
93 copy_rstorage(jcr, jcr->pool->storage, _("Pool resource"));
95 Dmsg2(dbglevel, "Read pool=%s (From %s)\n", jcr->rpool->name(), jcr->rpool_source);
97 jcr->start_time = time(NULL);
98 jcr->jr.StartTime = jcr->start_time;
99 jcr->jr.JobLevel = L_FULL; /* we want this to appear as a Full backup */
100 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
101 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
104 POOLMEM *jobids = get_pool_memory(PM_FNAME);
105 jcr->jr.JobLevel = L_VIRTUAL_FULL;
106 db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, jobids);
107 jcr->jr.JobLevel = L_FULL;
108 Dmsg1(10, "Accurate jobids=%s\n", jobids);
110 free_pool_memory(jobids);
111 Jmsg(jcr, M_FATAL, 0, _("Cannot find previous JobIds.\n"));
116 * Now we find the last job that ran and store it's info in
117 * the previous_jr record. We will set our times to the
118 * values from that job so that anything changed after that
119 * time will be picked up on the next backup.
121 p = strrchr(jobids, ','); /* find last jobid */
127 memset(&jcr->previous_jr, 0, sizeof(jcr->previous_jr));
128 jcr->previous_jr.JobId = str_to_int64(p);
129 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
130 Jmsg(jcr, M_FATAL, 0, _("Error getting Job record for previous Job: ERR=%s"),
131 db_strerror(jcr->db));
135 if (!create_bootstrap_file(jcr, jobids)) {
136 Jmsg(jcr, M_FATAL, 0, _("Could not get or create the FileSet record.\n"));
137 free_pool_memory(jobids);
140 free_pool_memory(jobids);
143 * If the original backup pool has a NextPool, make sure a
144 * record exists in the database. Note, in this case, we
145 * will be backing up from pool to pool->NextPool.
147 if (jcr->pool->NextPool) {
148 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->NextPool->name());
149 if (jcr->jr.PoolId == 0) {
153 if (!set_migration_wstorage(jcr, jcr->pool)) {
156 jcr->pool = jcr->pool->NextPool;
157 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
159 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
161 // create_clones(jcr);
167 * Do a virtual backup, which consolidates all previous backups into
168 * a sort of synthetic Full.
170 * Returns: false on failure
173 bool do_vbackup(JCR *jcr)
178 Dmsg2(100, "rstorage=%p wstorage=%p\n", jcr->rstorage, jcr->wstorage);
179 Dmsg2(100, "Read store=%s, write store=%s\n",
180 ((STORE *)jcr->rstorage->first())->name(),
181 ((STORE *)jcr->wstorage->first())->name());
182 /* ***FIXME*** we really should simply verify that the pools are different */
184 if (((STORE *)jcr->rstorage->first())->name() == ((STORE *)jcr->wstorage->first())->name()) {
185 Jmsg(jcr, M_FATAL, 0, _("Read storage \"%s\" same as write storage.\n"),
186 ((STORE *)jcr->rstorage->first())->name());
191 /* Print Job Start message */
192 Jmsg(jcr, M_INFO, 0, _("Start Virtual Backup JobId %s, Job=%s\n"),
193 edit_uint64(jcr->JobId, ed1), jcr->Job);
194 if (!jcr->accurate) {
195 Jmsg(jcr, M_WARNING, 0,
196 _("This Job is not an Accurate backup so is not equivalent to a Full backup.\n"));
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)) {
221 Dmsg0(100, "Storage daemon connection OK\n");
223 if (!send_bootstrap_file(jcr, sd) ||
224 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
229 * We re-update the job start record so that the start
230 * time is set after the run before job. This avoids
231 * that any files created by the run before job will
232 * be saved twice. They will be backed up in the current
233 * job, but not in the next one unless they are changed.
234 * Without this, they will be backed up in this job and
235 * in the next job run because in that case, their date
236 * is after the start of this run.
238 jcr->start_time = time(NULL);
239 jcr->jr.StartTime = jcr->start_time;
240 jcr->jr.JobTDate = jcr->start_time;
241 set_jcr_job_status(jcr, JS_Running);
243 /* Update job start record */
244 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
245 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
250 * Start the job prior to starting the message thread below
251 * to avoid two threads from using the BSOCK structure at
254 if (!sd->fsend("run")) {
259 * Now start a Storage daemon message thread
261 if (!start_storage_daemon_message_thread(jcr)) {
265 set_jcr_job_status(jcr, JS_Running);
267 /* Pickup Job termination data */
268 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
269 wait_for_storage_daemon_termination(jcr);
270 set_jcr_job_status(jcr, jcr->SDJobStatus);
271 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
272 if (jcr->JobStatus != JS_Terminated) {
276 vbackup_cleanup(jcr, jcr->JobStatus);
282 * Release resources allocated during backup.
284 void vbackup_cleanup(JCR *jcr, int TermCode)
286 char sdt[50], edt[50], schedt[50];
287 char ec1[30], ec3[30], ec4[30], compress[50];
288 char ec7[30], ec8[30], elapsed[50];
289 char term_code[100], sd_term_msg[100];
290 const char *term_msg;
291 int msg_type = M_INFO;
294 double kbps, compression;
296 POOL_MEM query(PM_MESSAGE);
298 Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
299 memset(&mr, 0, sizeof(mr));
300 memset(&cr, 0, sizeof(cr));
302 jcr->set_JobLevel(L_FULL); /* we want this to appear as a Full backup */
303 jcr->jr.JobLevel = L_FULL; /* we want this to appear as a Full backup */
304 jcr->JobFiles = jcr->SDJobFiles;
305 jcr->JobBytes = jcr->SDJobBytes;
306 update_job_end(jcr, TermCode);
308 /* Update final items to set them to the previous job's values */
309 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
310 "JobTDate=%s WHERE JobId=%s",
311 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
312 edit_uint64(jcr->previous_jr.JobTDate, ec1),
313 edit_uint64(jcr->JobId, ec3));
314 db_sql_query(jcr->db, query.c_str(), NULL, NULL);
316 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
317 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
318 db_strerror(jcr->db));
319 set_jcr_job_status(jcr, JS_ErrorTerminated);
322 bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
323 if (!db_get_client_record(jcr, jcr->db, &cr)) {
324 Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
325 db_strerror(jcr->db));
328 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
329 if (!db_get_media_record(jcr, jcr->db, &mr)) {
330 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
331 mr.VolumeName, db_strerror(jcr->db));
332 set_jcr_job_status(jcr, JS_ErrorTerminated);
335 update_bootstrap_file(jcr);
337 switch (jcr->JobStatus) {
339 if (jcr->Errors || jcr->SDErrors) {
340 term_msg = _("Backup OK -- with warnings");
342 term_msg = _("Backup OK");
346 case JS_ErrorTerminated:
347 term_msg = _("*** Backup Error ***");
348 msg_type = M_ERROR; /* Generate error message */
349 if (jcr->store_bsock) {
350 jcr->store_bsock->signal(BNET_TERMINATE);
351 if (jcr->SD_msg_chan) {
352 pthread_cancel(jcr->SD_msg_chan);
357 term_msg = _("Backup Canceled");
358 if (jcr->store_bsock) {
359 jcr->store_bsock->signal(BNET_TERMINATE);
360 if (jcr->SD_msg_chan) {
361 pthread_cancel(jcr->SD_msg_chan);
366 term_msg = term_code;
367 sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
370 bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
371 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
372 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
373 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
377 kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
379 if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
381 * Note, if the job has erred, most likely it did not write any
382 * tape, so suppress this "error" message since in that case
383 * it is normal. Or look at it the other way, only for a
384 * normal exit should we complain about this error.
386 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
387 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
389 jcr->VolumeName[0] = 0; /* none */
392 if (jcr->ReadBytes == 0) {
393 bstrncpy(compress, "None", sizeof(compress));
395 compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
396 if (compression < 0.5) {
397 bstrncpy(compress, "None", sizeof(compress));
399 bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
402 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
404 Jmsg(jcr, msg_type, 0, _("Bacula %s %s (%s): %s\n"
405 " Build OS: %s %s %s\n"
408 " Backup Level: Virtual Full\n"
409 " Client: \"%s\" %s\n"
410 " FileSet: \"%s\" %s\n"
411 " Pool: \"%s\" (From %s)\n"
412 " Catalog: \"%s\" (From %s)\n"
413 " Storage: \"%s\" (From %s)\n"
414 " Scheduled time: %s\n"
417 " Elapsed time: %s\n"
419 " SD Files Written: %s\n"
420 " SD Bytes Written: %s (%sB)\n"
424 " Volume name(s): %s\n"
425 " Volume Session Id: %d\n"
426 " Volume Session Time: %d\n"
427 " Last Volume Bytes: %s (%sB)\n"
429 " SD termination status: %s\n"
430 " Termination: %s\n\n"),
431 my_name, VERSION, LSMDATE, edt,
432 HOST_OS, DISTNAME, DISTVER,
435 jcr->client->name(), cr.Uname,
436 jcr->fileset->name(), jcr->FSCreateTime,
437 jcr->pool->name(), jcr->pool_source,
438 jcr->catalog->name(), jcr->catalog_source,
439 jcr->wstore->name(), jcr->wstore_source,
443 edit_utime(RunTime, elapsed, sizeof(elapsed)),
445 edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
446 edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
447 edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
449 jcr->Encrypt?_("yes"):_("no"),
450 jcr->accurate?_("yes"):_("no"),
454 edit_uint64_with_commas(mr.VolBytes, ec7),
455 edit_uint64_with_suffix(mr.VolBytes, ec8),
460 Dmsg0(100, "Leave vbackup_cleanup()\n");
464 * This callback routine is responsible for inserting the
465 * items it gets into the bootstrap structure. For each JobId selected
466 * this routine is called once for each file. We do not allow
467 * duplicate filenames, but instead keep the info from the most
468 * recent file entered (i.e. the JobIds are assumed to be sorted)
470 * See uar_sel_files in sql_cmds.c for query that calls us.
471 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
472 * row[3]=JobId row[4]=LStat
474 int insert_bootstrap_handler(void *ctx, int num_fields, char **row)
478 RBSR *bsr = (RBSR *)ctx;
480 JobId = str_to_int64(row[3]);
481 FileIndex = str_to_int64(row[2]);
482 add_findex(bsr, JobId, FileIndex);
487 static bool create_bootstrap_file(JCR *jcr, POOLMEM *jobids)
492 memset(&rx, 0, sizeof(rx));
494 ua = new_ua_context(jcr);
497 #define new_get_file_list
498 #ifdef new_get_file_list
499 if (!db_get_file_list(jcr, ua->db, jobids, insert_bootstrap_handler, (void *)rx.bsr)) {
500 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
504 JobId_t JobId, last_JobId = 0;
505 rx.query = get_pool_memory(PM_MESSAGE);
506 for (p=rx.JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
509 if (JobId == last_JobId) {
510 continue; /* eliminate duplicate JobIds */
514 * Find files for this JobId and insert them in the tree
516 Mmsg(rx.query, uar_sel_files, edit_int64(JobId, ed1));
517 Dmsg1(100, "uar_sel_files=%s\n", rx.query);
518 if (!db_sql_query(ua->db, rx.query, insert_bootstrap_handler, (void *)rx.bsr)) {
519 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
521 free_pool_memory(rx.query);
526 complete_bsr(ua, rx.bsr);
527 jcr->ExpectedFiles = write_bsr_file(ua, rx);
528 if (debug_level >= 10) {
529 Dmsg1(000, "Found %d files to consolidate.\n", jcr->ExpectedFiles);
531 if (jcr->ExpectedFiles == 0) {