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)
64 if (!get_or_create_fileset_record(jcr)) {
65 Dmsg1(dbglevel, "JobId=%d no FileSet\n", (int)jcr->JobId);
69 apply_pool_overrides(jcr);
71 if (!allow_duplicate_job(jcr)) {
76 * Note, at this point, pool is the pool for this job. We
77 * transfer it to rpool (read pool), and a bit later,
78 * pool will be changed to point to the write pool,
79 * which comes from pool->NextPool.
81 jcr->rpool = jcr->pool; /* save read pool */
82 pm_strcpy(jcr->rpool_source, jcr->pool_source);
85 Dmsg2(dbglevel, "Read pool=%s (From %s)\n", jcr->rpool->name(), jcr->rpool_source);
87 jcr->start_time = time(NULL);
88 jcr->jr.StartTime = jcr->start_time;
89 jcr->jr.JobLevel = L_FULL; /* we want this to appear as a Full backup */
90 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
91 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
94 POOLMEM *jobids = get_pool_memory(PM_FNAME);
95 jcr->jr.JobLevel = L_VIRTUAL_FULL;
96 db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, jobids);
97 jcr->jr.JobLevel = L_FULL;
98 Dmsg1(10, "Accurate jobids=%s\n", jobids);
100 free_pool_memory(jobids);
101 Jmsg(jcr, M_FATAL, 0, _("Cannot find previous JobIds.\n"));
105 if (!create_bootstrap_file(jcr, jobids)) {
106 Jmsg(jcr, M_FATAL, 0, _("Could not get or create the FileSet record.\n"));
107 free_pool_memory(jobids);
110 free_pool_memory(jobids);
113 * If the original backup pool has a NextPool, make sure a
114 * record exists in the database. Note, in this case, we
115 * will be backing up from pool to pool->NextPool.
117 if (jcr->pool->NextPool) {
118 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->NextPool->name());
119 if (jcr->jr.PoolId == 0) {
124 if (!set_migration_wstorage(jcr, jcr->pool)) {
127 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
129 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
137 * Do a backup of the specified FileSet
139 * Returns: false on failure
142 bool do_vbackup(JCR *jcr)
147 /* Print Job Start message */
148 Jmsg(jcr, M_INFO, 0, _("Start Vbackup JobId %s, Job=%s\n"),
149 edit_uint64(jcr->JobId, ed1), jcr->Job);
152 * Open a message channel connection with the Storage
153 * daemon. This is to let him know that our client
154 * will be contacting him for a backup session.
157 Dmsg0(110, "Open connection with storage daemon\n");
158 set_jcr_job_status(jcr, JS_WaitSD);
160 * Start conversation with Storage daemon
162 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
165 sd = jcr->store_bsock;
167 * Now start a job with the Storage daemon
169 Dmsg2(100, "rstorage=%p wstorage=%p\n", jcr->rstorage, jcr->wstorage);
170 Dmsg2(100, "Read store=%s, write store=%s\n",
171 ((STORE *)jcr->rstorage->first())->name(),
172 ((STORE *)jcr->wstorage->first())->name());
173 if (((STORE *)jcr->rstorage->first())->name() == ((STORE *)jcr->wstorage->first())->name()) {
174 Jmsg(jcr, M_FATAL, 0, _("Read storage \"%s\" same as write storage.\n"),
175 ((STORE *)jcr->rstorage->first())->name());
178 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage)) {
181 Dmsg0(100, "Storage daemon connection OK\n");
183 if (!send_bootstrap_file(jcr, sd) ||
184 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
189 * We re-update the job start record so that the start
190 * time is set after the run before job. This avoids
191 * that any files created by the run before job will
192 * be saved twice. They will be backed up in the current
193 * job, but not in the next one unless they are changed.
194 * Without this, they will be backed up in this job and
195 * in the next job run because in that case, their date
196 * is after the start of this run.
198 jcr->start_time = time(NULL);
199 jcr->jr.StartTime = jcr->start_time;
200 jcr->jr.JobTDate = jcr->start_time;
201 set_jcr_job_status(jcr, JS_Running);
203 /* Update job start record for this migration control job */
204 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
205 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
210 * Start the job prior to starting the message thread below
211 * to avoid two threads from using the BSOCK structure at
214 if (!sd->fsend("run")) {
219 * Now start a Storage daemon message thread
221 if (!start_storage_daemon_message_thread(jcr)) {
225 set_jcr_job_status(jcr, JS_Running);
227 /* Pickup Job termination data */
228 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
229 wait_for_storage_daemon_termination(jcr);
230 set_jcr_job_status(jcr, jcr->SDJobStatus);
231 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
232 if (jcr->JobStatus != JS_Terminated) {
236 vbackup_cleanup(jcr, jcr->JobStatus);
242 * Release resources allocated during backup.
244 void vbackup_cleanup(JCR *jcr, int TermCode)
246 char sdt[50], edt[50], schedt[50];
247 char ec1[30], ec3[30], ec4[30], compress[50];
248 char ec7[30], ec8[30], elapsed[50];
249 char term_code[100], sd_term_msg[100];
250 const char *term_msg;
251 int msg_type = M_INFO;
254 double kbps, compression;
257 Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
258 memset(&mr, 0, sizeof(mr));
259 memset(&cr, 0, sizeof(cr));
261 jcr->set_JobLevel(L_FULL); /* we want this to appear as a Full backup */
262 jcr->jr.JobLevel = L_FULL; /* we want this to appear as a Full backup */
263 jcr->JobFiles = jcr->SDJobFiles;
264 jcr->JobBytes = jcr->SDJobBytes;
265 update_job_end(jcr, TermCode);
268 /* ***FIXME*** set to time of last incremental */
269 /* Update final items to set them to the previous job's values */
270 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
271 "JobTDate=%s WHERE JobId=%s",
272 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
273 edit_uint64(jcr->previous_jr.JobTDate, ec1),
274 edit_uint64(mig_jcr->jr.JobId, ec2));
275 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
278 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
279 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
280 db_strerror(jcr->db));
281 set_jcr_job_status(jcr, JS_ErrorTerminated);
284 bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
285 if (!db_get_client_record(jcr, jcr->db, &cr)) {
286 Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
287 db_strerror(jcr->db));
290 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
291 if (!db_get_media_record(jcr, jcr->db, &mr)) {
292 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
293 mr.VolumeName, db_strerror(jcr->db));
294 set_jcr_job_status(jcr, JS_ErrorTerminated);
297 update_bootstrap_file(jcr);
299 switch (jcr->JobStatus) {
301 if (jcr->Errors || jcr->SDErrors) {
302 term_msg = _("Backup OK -- with warnings");
304 term_msg = _("Backup OK");
308 case JS_ErrorTerminated:
309 term_msg = _("*** Backup Error ***");
310 msg_type = M_ERROR; /* Generate error message */
311 if (jcr->store_bsock) {
312 jcr->store_bsock->signal(BNET_TERMINATE);
313 if (jcr->SD_msg_chan) {
314 pthread_cancel(jcr->SD_msg_chan);
319 term_msg = _("Backup Canceled");
320 if (jcr->store_bsock) {
321 jcr->store_bsock->signal(BNET_TERMINATE);
322 if (jcr->SD_msg_chan) {
323 pthread_cancel(jcr->SD_msg_chan);
328 term_msg = term_code;
329 sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
332 bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
333 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
334 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
335 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
339 kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
341 if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
343 * Note, if the job has erred, most likely it did not write any
344 * tape, so suppress this "error" message since in that case
345 * it is normal. Or look at it the other way, only for a
346 * normal exit should we complain about this error.
348 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
349 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
351 jcr->VolumeName[0] = 0; /* none */
354 if (jcr->ReadBytes == 0) {
355 bstrncpy(compress, "None", sizeof(compress));
357 compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
358 if (compression < 0.5) {
359 bstrncpy(compress, "None", sizeof(compress));
361 bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
364 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
366 Jmsg(jcr, msg_type, 0, _("Bacula %s %s (%s): %s\n"
367 " Build OS: %s %s %s\n"
370 " Backup Level: Virtual Full\n"
371 " Client: \"%s\" %s\n"
372 " FileSet: \"%s\" %s\n"
373 " Pool: \"%s\" (From %s)\n"
374 " Catalog: \"%s\" (From %s)\n"
375 " Storage: \"%s\" (From %s)\n"
376 " Scheduled time: %s\n"
379 " Elapsed time: %s\n"
381 " SD Files Written: %s\n"
382 " SD Bytes Written: %s (%sB)\n"
386 " Volume name(s): %s\n"
387 " Volume Session Id: %d\n"
388 " Volume Session Time: %d\n"
389 " Last Volume Bytes: %s (%sB)\n"
391 " SD termination status: %s\n"
392 " Termination: %s\n\n"),
393 my_name, VERSION, LSMDATE, edt,
394 HOST_OS, DISTNAME, DISTVER,
397 jcr->client->name(), cr.Uname,
398 jcr->fileset->name(), jcr->FSCreateTime,
399 jcr->pool->name(), jcr->pool_source,
400 jcr->catalog->name(), jcr->catalog_source,
401 jcr->wstore->name(), jcr->wstore_source,
405 edit_utime(RunTime, elapsed, sizeof(elapsed)),
407 edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
408 edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
409 edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
411 jcr->Encrypt?_("yes"):_("no"),
412 jcr->accurate?_("yes"):_("no"),
416 edit_uint64_with_commas(mr.VolBytes, ec7),
417 edit_uint64_with_suffix(mr.VolBytes, ec8),
422 Dmsg0(100, "Leave vbackup_cleanup()\n");
426 * This callback routine is responsible for inserting the
427 * items it gets into the bootstrap structure. For each JobId selected
428 * this routine is called once for each file. We do not allow
429 * duplicate filenames, but instead keep the info from the most
430 * recent file entered (i.e. the JobIds are assumed to be sorted)
432 * See uar_sel_files in sql_cmds.c for query that calls us.
433 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
434 * row[3]=JobId row[4]=LStat
436 int insert_bootstrap_handler(void *ctx, int num_fields, char **row)
440 RBSR *bsr = (RBSR *)ctx;
442 JobId = str_to_int64(row[3]);
443 FileIndex = str_to_int64(row[2]);
444 add_findex(bsr, JobId, FileIndex);
449 static bool create_bootstrap_file(JCR *jcr, POOLMEM *jobids)
454 memset(&rx, 0, sizeof(rx));
456 ua = new_ua_context(jcr);
459 #define new_get_file_list
460 #ifdef new_get_file_list
461 if (!db_get_file_list(jcr, ua->db, jobids, insert_bootstrap_handler, (void *)rx.bsr)) {
462 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
466 JobId_t JobId, last_JobId = 0;
467 rx.query = get_pool_memory(PM_MESSAGE);
468 for (p=rx.JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
471 if (JobId == last_JobId) {
472 continue; /* eliminate duplicate JobIds */
476 * Find files for this JobId and insert them in the tree
478 Mmsg(rx.query, uar_sel_files, edit_int64(JobId, ed1));
479 Dmsg1(100, "uar_sel_files=%s\n", rx.query);
480 if (!db_sql_query(ua->db, rx.query, insert_bootstrap_handler, (void *)rx.bsr)) {
481 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
483 free_pool_memory(rx.query);
488 complete_bsr(ua, rx.bsr);
489 jcr->ExpectedFiles = write_bsr_file(ua, rx);
490 if (debug_level >= 10) {
491 Dmsg1(000, "Found %d files to consolidate.\n", jcr->ExpectedFiles);
493 if (jcr->ExpectedFiles == 0) {