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 Dmsg1(10, "Previous JobId=%s\n", p);
130 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
131 Jmsg(jcr, M_FATAL, 0, _("Error getting Job record for previous Job: ERR=%s"),
132 db_strerror(jcr->db));
136 if (!create_bootstrap_file(jcr, jobids)) {
137 Jmsg(jcr, M_FATAL, 0, _("Could not get or create the FileSet record.\n"));
138 free_pool_memory(jobids);
141 free_pool_memory(jobids);
144 * If the original backup pool has a NextPool, make sure a
145 * record exists in the database. Note, in this case, we
146 * will be backing up from pool to pool->NextPool.
148 if (jcr->pool->NextPool) {
149 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->NextPool->name());
150 if (jcr->jr.PoolId == 0) {
154 if (!set_migration_wstorage(jcr, jcr->pool)) {
157 jcr->pool = jcr->pool->NextPool;
158 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
160 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
162 // create_clones(jcr);
168 * Do a virtual backup, which consolidates all previous backups into
169 * a sort of synthetic Full.
171 * Returns: false on failure
174 bool do_vbackup(JCR *jcr)
179 Dmsg2(100, "rstorage=%p wstorage=%p\n", jcr->rstorage, jcr->wstorage);
180 Dmsg2(100, "Read store=%s, write store=%s\n",
181 ((STORE *)jcr->rstorage->first())->name(),
182 ((STORE *)jcr->wstorage->first())->name());
183 /* ***FIXME*** we really should simply verify that the pools are different */
185 if (((STORE *)jcr->rstorage->first())->name() == ((STORE *)jcr->wstorage->first())->name()) {
186 Jmsg(jcr, M_FATAL, 0, _("Read storage \"%s\" same as write storage.\n"),
187 ((STORE *)jcr->rstorage->first())->name());
192 /* Print Job Start message */
193 Jmsg(jcr, M_INFO, 0, _("Start Virtual Backup JobId %s, Job=%s\n"),
194 edit_uint64(jcr->JobId, ed1), jcr->Job);
195 if (!jcr->accurate) {
196 Jmsg(jcr, M_WARNING, 0,
197 _("This Job is not an Accurate backup so is not equivalent to a Full backup.\n"));
201 * Open a message channel connection with the Storage
202 * daemon. This is to let him know that our client
203 * will be contacting him for a backup session.
206 Dmsg0(110, "Open connection with storage daemon\n");
207 set_jcr_job_status(jcr, JS_WaitSD);
209 * Start conversation with Storage daemon
211 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
214 sd = jcr->store_bsock;
217 * Now start a job with the Storage daemon
219 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage)) {
222 Dmsg0(100, "Storage daemon connection OK\n");
224 if (!send_bootstrap_file(jcr, sd) ||
225 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
230 * We re-update the job start record so that the start
231 * time is set after the run before job. This avoids
232 * that any files created by the run before job will
233 * be saved twice. They will be backed up in the current
234 * job, but not in the next one unless they are changed.
235 * Without this, they will be backed up in this job and
236 * in the next job run because in that case, their date
237 * is after the start of this run.
239 jcr->start_time = time(NULL);
240 jcr->jr.StartTime = jcr->start_time;
241 jcr->jr.JobTDate = jcr->start_time;
242 set_jcr_job_status(jcr, JS_Running);
244 /* Update job start record */
245 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
246 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
251 * Start the job prior to starting the message thread below
252 * to avoid two threads from using the BSOCK structure at
255 if (!sd->fsend("run")) {
260 * Now start a Storage daemon message thread
262 if (!start_storage_daemon_message_thread(jcr)) {
266 set_jcr_job_status(jcr, JS_Running);
268 /* Pickup Job termination data */
269 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
270 wait_for_storage_daemon_termination(jcr);
271 set_jcr_job_status(jcr, jcr->SDJobStatus);
272 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
273 if (jcr->JobStatus != JS_Terminated) {
277 vbackup_cleanup(jcr, jcr->JobStatus);
283 * Release resources allocated during backup.
285 void vbackup_cleanup(JCR *jcr, int TermCode)
287 char sdt[50], edt[50], schedt[50];
288 char ec1[30], ec3[30], ec4[30], compress[50];
289 char ec7[30], ec8[30], elapsed[50];
290 char term_code[100], sd_term_msg[100];
291 const char *term_msg;
292 int msg_type = M_INFO;
295 double kbps, compression;
297 POOL_MEM query(PM_MESSAGE);
299 Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
300 memset(&mr, 0, sizeof(mr));
301 memset(&cr, 0, sizeof(cr));
303 jcr->set_JobLevel(L_FULL); /* we want this to appear as a Full backup */
304 jcr->jr.JobLevel = L_FULL; /* we want this to appear as a Full backup */
305 jcr->JobFiles = jcr->SDJobFiles;
306 jcr->JobBytes = jcr->SDJobBytes;
307 update_job_end(jcr, TermCode);
309 /* Update final items to set them to the previous job's values */
310 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
311 "JobTDate=%s WHERE JobId=%s",
312 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
313 edit_uint64(jcr->previous_jr.JobTDate, ec1),
314 edit_uint64(jcr->JobId, ec3));
315 db_sql_query(jcr->db, query.c_str(), NULL, NULL);
317 /* Get the fully updated job record */
318 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
319 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
320 db_strerror(jcr->db));
321 set_jcr_job_status(jcr, JS_ErrorTerminated);
324 bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
325 if (!db_get_client_record(jcr, jcr->db, &cr)) {
326 Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
327 db_strerror(jcr->db));
330 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
331 if (!db_get_media_record(jcr, jcr->db, &mr)) {
332 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
333 mr.VolumeName, db_strerror(jcr->db));
334 set_jcr_job_status(jcr, JS_ErrorTerminated);
337 update_bootstrap_file(jcr);
339 switch (jcr->JobStatus) {
341 if (jcr->Errors || jcr->SDErrors) {
342 term_msg = _("Backup OK -- with warnings");
344 term_msg = _("Backup OK");
348 case JS_ErrorTerminated:
349 term_msg = _("*** Backup Error ***");
350 msg_type = M_ERROR; /* Generate error message */
351 if (jcr->store_bsock) {
352 jcr->store_bsock->signal(BNET_TERMINATE);
353 if (jcr->SD_msg_chan) {
354 pthread_cancel(jcr->SD_msg_chan);
359 term_msg = _("Backup Canceled");
360 if (jcr->store_bsock) {
361 jcr->store_bsock->signal(BNET_TERMINATE);
362 if (jcr->SD_msg_chan) {
363 pthread_cancel(jcr->SD_msg_chan);
368 term_msg = term_code;
369 sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
372 bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
373 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
374 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
375 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
379 kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
381 if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
383 * Note, if the job has erred, most likely it did not write any
384 * tape, so suppress this "error" message since in that case
385 * it is normal. Or look at it the other way, only for a
386 * normal exit should we complain about this error.
388 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
389 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
391 jcr->VolumeName[0] = 0; /* none */
394 if (jcr->ReadBytes == 0) {
395 bstrncpy(compress, "None", sizeof(compress));
397 compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
398 if (compression < 0.5) {
399 bstrncpy(compress, "None", sizeof(compress));
401 bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
404 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
406 Jmsg(jcr, msg_type, 0, _("Bacula %s %s (%s): %s\n"
407 " Build OS: %s %s %s\n"
410 " Backup Level: Virtual Full\n"
411 " Client: \"%s\" %s\n"
412 " FileSet: \"%s\" %s\n"
413 " Pool: \"%s\" (From %s)\n"
414 " Catalog: \"%s\" (From %s)\n"
415 " Storage: \"%s\" (From %s)\n"
416 " Scheduled time: %s\n"
419 " Elapsed time: %s\n"
421 " SD Files Written: %s\n"
422 " 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),
452 edit_uint64_with_commas(mr.VolBytes, ec7),
453 edit_uint64_with_suffix(mr.VolBytes, ec8),
458 Dmsg0(100, "Leave vbackup_cleanup()\n");
462 * This callback routine is responsible for inserting the
463 * items it gets into the bootstrap structure. For each JobId selected
464 * this routine is called once for each file. We do not allow
465 * duplicate filenames, but instead keep the info from the most
466 * recent file entered (i.e. the JobIds are assumed to be sorted)
468 * See uar_sel_files in sql_cmds.c for query that calls us.
469 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
470 * row[3]=JobId row[4]=LStat
472 int insert_bootstrap_handler(void *ctx, int num_fields, char **row)
476 RBSR *bsr = (RBSR *)ctx;
478 JobId = str_to_int64(row[3]);
479 FileIndex = str_to_int64(row[2]);
480 add_findex(bsr, JobId, FileIndex);
485 static bool create_bootstrap_file(JCR *jcr, POOLMEM *jobids)
490 memset(&rx, 0, sizeof(rx));
492 ua = new_ua_context(jcr);
495 #define new_get_file_list
496 #ifdef new_get_file_list
497 if (!db_get_file_list(jcr, ua->db, jobids, insert_bootstrap_handler, (void *)rx.bsr)) {
498 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
502 JobId_t JobId, last_JobId = 0;
503 rx.query = get_pool_memory(PM_MESSAGE);
504 for (p=rx.JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
507 if (JobId == last_JobId) {
508 continue; /* eliminate duplicate JobIds */
512 * Find files for this JobId and insert them in the tree
514 Mmsg(rx.query, uar_sel_files, edit_int64(JobId, ed1));
515 Dmsg1(100, "uar_sel_files=%s\n", rx.query);
516 if (!db_sql_query(ua->db, rx.query, insert_bootstrap_handler, (void *)rx.bsr)) {
517 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
519 free_pool_memory(rx.query);
524 complete_bsr(ua, rx.bsr);
525 jcr->ExpectedFiles = write_bsr_file(ua, rx);
526 if (debug_level >= 10) {
527 Dmsg1(000, "Found %d files to consolidate.\n", jcr->ExpectedFiles);
529 if (jcr->ExpectedFiles == 0) {