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 db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, jobids);
96 Dmsg1(100, "Accurate jobids=%s\n", jobids);
98 free_pool_memory(jobids);
99 Jmsg(jcr, M_FATAL, 0, _("Cannot find previous JobIds.\n"));
103 if (!create_bootstrap_file(jcr, jobids)) {
104 Jmsg(jcr, M_FATAL, 0, _("Could not get or create the FileSet record.\n"));
105 free_pool_memory(jobids);
108 free_pool_memory(jobids);
111 * If the original backup pool has a NextPool, make sure a
112 * record exists in the database. Note, in this case, we
113 * will be backing up from pool to pool->NextPool.
115 if (jcr->pool->NextPool) {
116 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->NextPool->name());
117 if (jcr->jr.PoolId == 0) {
122 if (!set_migration_wstorage(jcr, jcr->pool)) {
125 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
127 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
135 * Do a backup of the specified FileSet
137 * Returns: false on failure
140 bool do_vbackup(JCR *jcr)
145 /* Print Job Start message */
146 Jmsg(jcr, M_INFO, 0, _("Start Vbackup JobId %s, Job=%s\n"),
147 edit_uint64(jcr->JobId, ed1), jcr->Job);
150 * Open a message channel connection with the Storage
151 * daemon. This is to let him know that our client
152 * will be contacting him for a backup session.
155 Dmsg0(110, "Open connection with storage daemon\n");
156 set_jcr_job_status(jcr, JS_WaitSD);
158 * Start conversation with Storage daemon
160 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
163 sd = jcr->store_bsock;
165 * Now start a job with the Storage daemon
167 Dmsg2(100, "rstorage=%p wstorage=%p\n", jcr->rstorage, jcr->wstorage);
168 Dmsg2(100, "Read store=%s, write store=%s\n",
169 ((STORE *)jcr->rstorage->first())->name(),
170 ((STORE *)jcr->wstorage->first())->name());
171 if (((STORE *)jcr->rstorage->first())->name() == ((STORE *)jcr->wstorage->first())->name()) {
172 Jmsg(jcr, M_FATAL, 0, _("Read storage \"%s\" same as write storage.\n"),
173 ((STORE *)jcr->rstorage->first())->name());
176 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage)) {
179 Dmsg0(100, "Storage daemon connection OK\n");
181 if (!send_bootstrap_file(jcr, sd) ||
182 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
187 * We re-update the job start record so that the start
188 * time is set after the run before job. This avoids
189 * that any files created by the run before job will
190 * be saved twice. They will be backed up in the current
191 * job, but not in the next one unless they are changed.
192 * Without this, they will be backed up in this job and
193 * in the next job run because in that case, their date
194 * is after the start of this run.
196 jcr->start_time = time(NULL);
197 jcr->jr.StartTime = jcr->start_time;
198 jcr->jr.JobTDate = jcr->start_time;
199 set_jcr_job_status(jcr, JS_Running);
201 /* Update job start record for this migration control job */
202 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
203 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
208 * Start the job prior to starting the message thread below
209 * to avoid two threads from using the BSOCK structure at
212 if (!sd->fsend("run")) {
217 * Now start a Storage daemon message thread
219 if (!start_storage_daemon_message_thread(jcr)) {
223 set_jcr_job_status(jcr, JS_Running);
225 /* Pickup Job termination data */
226 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
227 wait_for_storage_daemon_termination(jcr);
228 set_jcr_job_status(jcr, jcr->SDJobStatus);
229 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
230 if (jcr->JobStatus != JS_Terminated) {
234 vbackup_cleanup(jcr, jcr->JobStatus);
240 * Release resources allocated during backup.
242 void vbackup_cleanup(JCR *jcr, int TermCode)
244 char sdt[50], edt[50], schedt[50];
245 char ec1[30], ec3[30], ec4[30], compress[50];
246 char ec7[30], ec8[30], elapsed[50];
247 char term_code[100], fd_term_msg[100], sd_term_msg[100];
248 const char *term_msg;
249 int msg_type = M_INFO;
252 double kbps, compression;
255 Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
256 memset(&mr, 0, sizeof(mr));
257 memset(&cr, 0, sizeof(cr));
259 jcr->set_JobLevel(L_FULL); /* we want this to appear as a Full backup */
260 jcr->jr.JobLevel = L_FULL; /* we want this to appear as a Full backup */
261 jcr->JobFiles = jcr->SDJobFiles;
262 jcr->JobBytes = jcr->SDJobBytes;
263 update_job_end(jcr, TermCode);
266 /* ***FIXME*** set to time of last incremental */
267 /* Update final items to set them to the previous job's values */
268 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
269 "JobTDate=%s WHERE JobId=%s",
270 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
271 edit_uint64(jcr->previous_jr.JobTDate, ec1),
272 edit_uint64(mig_jcr->jr.JobId, ec2));
273 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
276 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
277 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
278 db_strerror(jcr->db));
279 set_jcr_job_status(jcr, JS_ErrorTerminated);
282 bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
283 if (!db_get_client_record(jcr, jcr->db, &cr)) {
284 Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
285 db_strerror(jcr->db));
288 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
289 if (!db_get_media_record(jcr, jcr->db, &mr)) {
290 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
291 mr.VolumeName, db_strerror(jcr->db));
292 set_jcr_job_status(jcr, JS_ErrorTerminated);
295 update_bootstrap_file(jcr);
297 switch (jcr->JobStatus) {
299 if (jcr->Errors || jcr->SDErrors) {
300 term_msg = _("Backup OK -- with warnings");
302 term_msg = _("Backup OK");
306 case JS_ErrorTerminated:
307 term_msg = _("*** Backup Error ***");
308 msg_type = M_ERROR; /* Generate error message */
309 if (jcr->store_bsock) {
310 jcr->store_bsock->signal(BNET_TERMINATE);
311 if (jcr->SD_msg_chan) {
312 pthread_cancel(jcr->SD_msg_chan);
317 term_msg = _("Backup Canceled");
318 if (jcr->store_bsock) {
319 jcr->store_bsock->signal(BNET_TERMINATE);
320 if (jcr->SD_msg_chan) {
321 pthread_cancel(jcr->SD_msg_chan);
326 term_msg = term_code;
327 sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
330 bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
331 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
332 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
333 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
337 kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
339 if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
341 * Note, if the job has erred, most likely it did not write any
342 * tape, so suppress this "error" message since in that case
343 * it is normal. Or look at it the other way, only for a
344 * normal exit should we complain about this error.
346 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
347 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
349 jcr->VolumeName[0] = 0; /* none */
352 if (jcr->ReadBytes == 0) {
353 bstrncpy(compress, "None", sizeof(compress));
355 compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
356 if (compression < 0.5) {
357 bstrncpy(compress, "None", sizeof(compress));
359 bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
362 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
363 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
365 // bmicrosleep(15, 0); /* for debugging SIGHUP */
367 Jmsg(jcr, msg_type, 0, _("Bacula %s %s (%s): %s\n"
368 " Build OS: %s %s %s\n"
371 " Backup Level: %s%s\n"
372 " Client: \"%s\" %s\n"
373 " FileSet: \"%s\" %s\n"
374 " Pool: \"%s\" (From %s)\n"
375 " Catalog: \"%s\" (From %s)\n"
376 " Storage: \"%s\" (From %s)\n"
377 " Scheduled time: %s\n"
380 " Elapsed time: %s\n"
382 " SD Files Written: %s\n"
383 " SD Bytes Written: %s (%sB)\n"
385 " Software Compression: %s\n"
389 " Volume name(s): %s\n"
390 " Volume Session Id: %d\n"
391 " Volume Session Time: %d\n"
392 " Last Volume Bytes: %s (%sB)\n"
394 " SD termination status: %s\n"
395 " Termination: %s\n\n"),
396 my_name, VERSION, LSMDATE, edt,
397 HOST_OS, DISTNAME, DISTVER,
400 level_to_str(jcr->get_JobLevel()), jcr->since,
401 jcr->client->name(), cr.Uname,
402 jcr->fileset->name(), jcr->FSCreateTime,
403 jcr->pool->name(), jcr->pool_source,
404 jcr->catalog->name(), jcr->catalog_source,
405 jcr->wstore->name(), jcr->wstore_source,
409 edit_utime(RunTime, elapsed, sizeof(elapsed)),
411 edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
412 edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
413 edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
416 jcr->VSS?_("yes"):_("no"),
417 jcr->Encrypt?_("yes"):_("no"),
418 jcr->accurate?_("yes"):_("no"),
422 edit_uint64_with_commas(mr.VolBytes, ec7),
423 edit_uint64_with_suffix(mr.VolBytes, ec8),
428 Dmsg0(100, "Leave vbackup_cleanup()\n");
432 * This callback routine is responsible for inserting the
433 * items it gets into the bootstrap structure. For each JobId selected
434 * this routine is called once for each file. We do not allow
435 * duplicate filenames, but instead keep the info from the most
436 * recent file entered (i.e. the JobIds are assumed to be sorted)
438 * See uar_sel_files in sql_cmds.c for query that calls us.
439 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
440 * row[3]=JobId row[4]=LStat
442 int insert_bootstrap_handler(void *ctx, int num_fields, char **row)
446 RBSR *bsr = (RBSR *)ctx;
448 JobId = str_to_int64(row[3]);
449 FileIndex = str_to_int64(row[2]);
450 add_findex(bsr, JobId, FileIndex);
455 static bool create_bootstrap_file(JCR *jcr, POOLMEM *jobids)
460 memset(&rx, 0, sizeof(rx));
462 ua = new_ua_context(jcr);
465 #define new_get_file_list
466 #ifdef new_get_file_list
467 if (!db_get_file_list(jcr, ua->db, jobids, insert_bootstrap_handler, (void *)rx.bsr)) {
468 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
472 JobId_t JobId, last_JobId = 0;
473 rx.query = get_pool_memory(PM_MESSAGE);
474 for (p=rx.JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
477 if (JobId == last_JobId) {
478 continue; /* eliminate duplicate JobIds */
482 * Find files for this JobId and insert them in the tree
484 Mmsg(rx.query, uar_sel_files, edit_int64(JobId, ed1));
485 Dmsg1(100, "uar_sel_files=%s\n", rx.query);
486 if (!db_sql_query(ua->db, rx.query, insert_bootstrap_handler, (void *)rx.bsr)) {
487 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
489 free_pool_memory(rx.query);
494 complete_bsr(ua, rx.bsr);
495 // Dmsg0(000, "Print bsr\n");
496 // print_bsr(ua, rx.bsr);
498 jcr->ExpectedFiles = write_bsr_file(ua, rx);
499 Dmsg1(000, "Found %d files to consolidate.\n", jcr->ExpectedFiles);
500 if (jcr->ExpectedFiles == 0) {