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 /* ***FIXME*** remove when implemented in job.c */
65 if (!jcr->rpool_source) {
66 jcr->rpool_source = get_pool_memory(PM_MESSAGE);
67 pm_strcpy(jcr->rpool_source, _("unknown source"));
70 if (!get_or_create_fileset_record(jcr)) {
71 Dmsg1(dbglevel, "JobId=%d no FileSet\n", (int)jcr->JobId);
75 apply_pool_overrides(jcr);
77 if (!allow_duplicate_job(jcr)) {
82 * Note, at this point, pool is the pool for this job. We
83 * transfer it to rpool (read pool), and a bit later,
84 * pool will be changed to point to the write pool,
85 * which comes from pool->NextPool.
87 jcr->rpool = jcr->pool; /* save read pool */
88 pm_strcpy(jcr->rpool_source, jcr->pool_source);
91 Dmsg2(dbglevel, "Read pool=%s (From %s)\n", jcr->rpool->name(), jcr->rpool_source);
93 jcr->start_time = time(NULL);
94 jcr->jr.StartTime = jcr->start_time;
95 jcr->jr.JobLevel = L_FULL; /* we want this to appear as a Full backup */
96 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
97 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
100 POOLMEM *jobids = get_pool_memory(PM_FNAME);
101 db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, jobids);
102 Dmsg1(000, "Accurate jobids=%s\n", jobids);
104 free_pool_memory(jobids);
105 Jmsg(jcr, M_FATAL, 0, _("Cannot find previous JobIds.\n"));
109 if (!create_bootstrap_file(jcr, jobids)) {
110 Jmsg(jcr, M_FATAL, 0, _("Could not get or create the FileSet record.\n"));
111 free_pool_memory(jobids);
114 free_pool_memory(jobids);
117 * If the original backup pool has a NextPool, make sure a
118 * record exists in the database. Note, in this case, we
119 * will be backing up from pool to pool->NextPool.
121 if (jcr->pool->NextPool) {
122 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->NextPool->name());
123 if (jcr->jr.PoolId == 0) {
127 /* ***FIXME*** this is probably not needed */
128 if (!set_migration_wstorage(jcr, jcr->pool)) {
131 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
133 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
141 * Do a backup of the specified FileSet
143 * Returns: false on failure
146 bool do_vbackup(JCR *jcr)
151 /* Print Job Start message */
152 Jmsg(jcr, M_INFO, 0, _("Start Vbackup JobId %s, Job=%s\n"),
153 edit_uint64(jcr->JobId, ed1), jcr->Job);
156 * Open a message channel connection with the Storage
157 * daemon. This is to let him know that our client
158 * will be contacting him for a backup session.
161 Dmsg0(110, "Open connection with storage daemon\n");
162 set_jcr_job_status(jcr, JS_WaitSD);
164 * Start conversation with Storage daemon
166 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
169 sd = jcr->store_bsock;
171 * Now start a job with the Storage daemon
173 Dmsg2(000, "Read store=%s, write store=%s\n",
174 ((STORE *)jcr->rstorage->first())->name(),
175 ((STORE *)jcr->wstorage->first())->name());
176 if (((STORE *)jcr->rstorage->first())->name() == ((STORE *)jcr->wstorage->first())->name()) {
177 Jmsg(jcr, M_FATAL, 0, _("Read storage \"%s\" same as write storage.\n"),
178 ((STORE *)jcr->rstorage->first())->name());
181 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage)) {
184 Dmsg0(000, "Storage daemon connection OK\n");
186 if (!send_bootstrap_file(jcr, sd) ||
187 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
191 Dmsg0(000, "Bootstrap file sent\n");
194 * We re-update the job start record so that the start
195 * time is set after the run before job. This avoids
196 * that any files created by the run before job will
197 * be saved twice. They will be backed up in the current
198 * job, but not in the next one unless they are changed.
199 * Without this, they will be backed up in this job and
200 * in the next job run because in that case, their date
201 * is after the start of this run.
203 jcr->start_time = time(NULL);
204 jcr->jr.StartTime = jcr->start_time;
205 jcr->jr.JobTDate = jcr->start_time;
206 set_jcr_job_status(jcr, JS_Running);
208 /* Update job start record for this migration control job */
209 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
210 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
215 * Start the job prior to starting the message thread below
216 * to avoid two threads from using the BSOCK structure at
219 if (!sd->fsend("run")) {
224 * Now start a Storage daemon message thread
226 if (!start_storage_daemon_message_thread(jcr)) {
230 set_jcr_job_status(jcr, JS_Running);
232 /* Pickup Job termination data */
233 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
234 wait_for_storage_daemon_termination(jcr);
235 set_jcr_job_status(jcr, jcr->SDJobStatus);
236 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
237 if (jcr->JobStatus != JS_Terminated) {
241 vbackup_cleanup(jcr, jcr->JobStatus);
247 * Release resources allocated during backup.
249 void vbackup_cleanup(JCR *jcr, int TermCode)
251 char sdt[50], edt[50], schedt[50];
252 char ec1[30], ec3[30], ec4[30], compress[50];
253 char ec7[30], ec8[30], elapsed[50];
254 char term_code[100], fd_term_msg[100], sd_term_msg[100];
255 const char *term_msg;
256 int msg_type = M_INFO;
259 double kbps, compression;
262 Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
263 memset(&mr, 0, sizeof(mr));
264 memset(&cr, 0, sizeof(cr));
266 jcr->JobLevel = L_FULL; /* we want this to appear as a Full backup */
267 jcr->jr.JobLevel = L_FULL; /* we want this to appear as a Full backup */
268 jcr->JobFiles = jcr->SDJobFiles;
269 jcr->JobBytes = jcr->SDJobBytes;
270 update_job_end(jcr, TermCode);
273 /* ***FIXME*** set to time of last incremental */
274 /* Update final items to set them to the previous job's values */
275 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
276 "JobTDate=%s WHERE JobId=%s",
277 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
278 edit_uint64(jcr->previous_jr.JobTDate, ec1),
279 edit_uint64(mig_jcr->jr.JobId, ec2));
280 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
283 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
284 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
285 db_strerror(jcr->db));
286 set_jcr_job_status(jcr, JS_ErrorTerminated);
289 bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
290 if (!db_get_client_record(jcr, jcr->db, &cr)) {
291 Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
292 db_strerror(jcr->db));
295 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
296 if (!db_get_media_record(jcr, jcr->db, &mr)) {
297 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
298 mr.VolumeName, db_strerror(jcr->db));
299 set_jcr_job_status(jcr, JS_ErrorTerminated);
302 update_bootstrap_file(jcr);
304 switch (jcr->JobStatus) {
306 if (jcr->Errors || jcr->SDErrors) {
307 term_msg = _("Backup OK -- with warnings");
309 term_msg = _("Backup OK");
313 case JS_ErrorTerminated:
314 term_msg = _("*** Backup Error ***");
315 msg_type = M_ERROR; /* Generate error message */
316 if (jcr->store_bsock) {
317 jcr->store_bsock->signal(BNET_TERMINATE);
318 if (jcr->SD_msg_chan) {
319 pthread_cancel(jcr->SD_msg_chan);
324 term_msg = _("Backup Canceled");
325 if (jcr->store_bsock) {
326 jcr->store_bsock->signal(BNET_TERMINATE);
327 if (jcr->SD_msg_chan) {
328 pthread_cancel(jcr->SD_msg_chan);
333 term_msg = term_code;
334 sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
337 bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
338 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
339 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
340 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
344 kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
346 if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
348 * Note, if the job has erred, most likely it did not write any
349 * tape, so suppress this "error" message since in that case
350 * it is normal. Or look at it the other way, only for a
351 * normal exit should we complain about this error.
353 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
354 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
356 jcr->VolumeName[0] = 0; /* none */
359 if (jcr->ReadBytes == 0) {
360 bstrncpy(compress, "None", sizeof(compress));
362 compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
363 if (compression < 0.5) {
364 bstrncpy(compress, "None", sizeof(compress));
366 bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
369 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
370 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
372 // bmicrosleep(15, 0); /* for debugging SIGHUP */
374 Jmsg(jcr, msg_type, 0, _("Bacula %s %s (%s): %s\n"
375 " Build OS: %s %s %s\n"
378 " Backup Level: %s%s\n"
379 " Client: \"%s\" %s\n"
380 " FileSet: \"%s\" %s\n"
381 " Pool: \"%s\" (From %s)\n"
382 " Catalog: \"%s\" (From %s)\n"
383 " Storage: \"%s\" (From %s)\n"
384 " Scheduled time: %s\n"
387 " Elapsed time: %s\n"
389 " SD Files Written: %s\n"
390 " SD Bytes Written: %s (%sB)\n"
392 " Software Compression: %s\n"
396 " Volume name(s): %s\n"
397 " Volume Session Id: %d\n"
398 " Volume Session Time: %d\n"
399 " Last Volume Bytes: %s (%sB)\n"
401 " SD termination status: %s\n"
402 " Termination: %s\n\n"),
403 my_name, VERSION, LSMDATE, edt,
404 HOST_OS, DISTNAME, DISTVER,
407 level_to_str(jcr->JobLevel), jcr->since,
408 jcr->client->name(), cr.Uname,
409 jcr->fileset->name(), jcr->FSCreateTime,
410 jcr->pool->name(), jcr->pool_source,
411 jcr->catalog->name(), jcr->catalog_source,
412 jcr->wstore->name(), jcr->wstore_source,
416 edit_utime(RunTime, elapsed, sizeof(elapsed)),
418 edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
419 edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
420 edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
423 jcr->VSS?_("yes"):_("no"),
424 jcr->Encrypt?_("yes"):_("no"),
425 jcr->accurate?_("yes"):_("no"),
429 edit_uint64_with_commas(mr.VolBytes, ec7),
430 edit_uint64_with_suffix(mr.VolBytes, ec8),
435 Dmsg0(100, "Leave vbackup_cleanup()\n");
439 * This callback routine is responsible for inserting the
440 * items it gets into the bootstrap structure. For each JobId selected
441 * this routine is called once for each file. We do not allow
442 * duplicate filenames, but instead keep the info from the most
443 * recent file entered (i.e. the JobIds are assumed to be sorted)
445 * See uar_sel_files in sql_cmds.c for query that calls us.
446 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
447 * row[3]=JobId row[4]=LStat
449 int insert_bootstrap_handler(void *ctx, int num_fields, char **row)
453 RBSR *bsr = (RBSR *)ctx;
455 JobId = str_to_int64(row[3]);
456 FileIndex = str_to_int64(row[2]);
457 add_findex(bsr, JobId, FileIndex);
462 static bool create_bootstrap_file(JCR *jcr, POOLMEM *jobids)
467 memset(&rx, 0, sizeof(rx));
469 ua = new_ua_context(jcr);
472 #define new_get_file_list
473 #ifdef new_get_file_list
474 if (!db_get_file_list(jcr, ua->db, jobids, insert_bootstrap_handler, (void *)rx.bsr)) {
475 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
479 JobId_t JobId, last_JobId = 0;
480 rx.query = get_pool_memory(PM_MESSAGE);
481 for (p=rx.JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
484 if (JobId == last_JobId) {
485 continue; /* eliminate duplicate JobIds */
489 * Find files for this JobId and insert them in the tree
491 Mmsg(rx.query, uar_sel_files, edit_int64(JobId, ed1));
492 Dmsg1(000, "uar_sel_files=%s\n", rx.query);
493 if (!db_sql_query(ua->db, rx.query, insert_bootstrap_handler, (void *)rx.bsr)) {
494 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
496 free_pool_memory(rx.query);
501 complete_bsr(ua, rx.bsr);
502 Dmsg0(000, "Print bsr\n");
503 print_bsr(ua, rx.bsr);
505 jcr->ExpectedFiles = write_bsr_file(ua, rx);
506 Dmsg1(000, "Found %d files to consolidate.\n", jcr->ExpectedFiles);
507 if (jcr->ExpectedFiles == 0) {