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 POOLMEM *jobids = get_pool_memory(PM_FNAME);
94 db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, jobids);
95 Dmsg1(000, "Accurate jobids=%s\n", jobids);
97 free_pool_memory(jobids);
98 Jmsg(jcr, M_FATAL, 0, _("Cannot find previous JobIds.\n"));
102 if (!create_bootstrap_file(jcr, jobids)) {
103 Jmsg(jcr, M_FATAL, 0, _("Could not get or create the FileSet record.\n"));
104 free_pool_memory(jobids);
107 free_pool_memory(jobids);
110 * If the original backup pool has a NextPool, make sure a
111 * record exists in the database. Note, in this case, we
112 * will be backing up from pool to pool->NextPool.
114 if (jcr->pool->NextPool) {
115 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->NextPool->name());
116 if (jcr->jr.PoolId == 0) {
120 /* ***FIXME*** this is probably not needed */
121 if (!set_migration_wstorage(jcr, jcr->pool)) {
124 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
126 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
134 * Do a backup of the specified FileSet
136 * Returns: false on failure
139 bool do_vbackup(JCR *jcr)
144 /* Print Job Start message */
145 Jmsg(jcr, M_INFO, 0, _("Start Vbackup JobId %s, Job=%s\n"),
146 edit_uint64(jcr->JobId, ed1), jcr->Job);
149 * Open a message channel connection with the Storage
150 * daemon. This is to let him know that our client
151 * will be contacting him for a backup session.
154 Dmsg0(110, "Open connection with storage daemon\n");
155 set_jcr_job_status(jcr, JS_WaitSD);
157 * Start conversation with Storage daemon
159 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
162 sd = jcr->store_bsock;
164 * Now start a job with the Storage daemon
166 Dmsg2(000, "Read store=%s, write store=%s\n",
167 ((STORE *)jcr->rstorage->first())->name(),
168 ((STORE *)jcr->wstorage->first())->name());
169 if (((STORE *)jcr->rstorage->first())->name() == ((STORE *)jcr->wstorage->first())->name()) {
170 Jmsg(jcr, M_FATAL, 0, _("Read storage \"%s\" same as write storage.\n"),
171 ((STORE *)jcr->rstorage->first())->name());
174 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage)) {
177 Dmsg0(150, "Storage daemon connection OK\n");
179 if (!send_bootstrap_file(jcr, sd) ||
180 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
185 * We re-update the job start record so that the start
186 * time is set after the run before job. This avoids
187 * that any files created by the run before job will
188 * be saved twice. They will be backed up in the current
189 * job, but not in the next one unless they are changed.
190 * Without this, they will be backed up in this job and
191 * in the next job run because in that case, their date
192 * is after the start of this run.
194 jcr->start_time = time(NULL);
195 jcr->jr.StartTime = jcr->start_time;
196 jcr->jr.JobTDate = jcr->start_time;
197 set_jcr_job_status(jcr, JS_Running);
199 /* Update job start record for this migration control job */
200 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
201 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
206 * Start the job prior to starting the message thread below
207 * to avoid two threads from using the BSOCK structure at
210 if (!sd->fsend("run")) {
215 * Now start a Storage daemon message thread
217 if (!start_storage_daemon_message_thread(jcr)) {
222 set_jcr_job_status(jcr, JS_Running);
224 /* Pickup Job termination data */
225 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
226 wait_for_storage_daemon_termination(jcr);
227 set_jcr_job_status(jcr, jcr->SDJobStatus);
228 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
229 if (jcr->JobStatus != JS_Terminated) {
233 vbackup_cleanup(jcr, jcr->JobStatus);
239 * Release resources allocated during backup.
241 void vbackup_cleanup(JCR *jcr, int TermCode)
243 char sdt[50], edt[50], schedt[50];
244 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], compress[50];
245 char ec6[30], ec7[30], ec8[30], elapsed[50];
246 char term_code[100], fd_term_msg[100], sd_term_msg[100];
247 const char *term_msg;
248 int msg_type = M_INFO;
251 double kbps, compression;
254 Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
255 memset(&mr, 0, sizeof(mr));
256 memset(&cr, 0, sizeof(cr));
258 update_job_end(jcr, TermCode);
260 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
261 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
262 db_strerror(jcr->db));
263 set_jcr_job_status(jcr, JS_ErrorTerminated);
266 bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
267 if (!db_get_client_record(jcr, jcr->db, &cr)) {
268 Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
269 db_strerror(jcr->db));
272 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
273 if (!db_get_media_record(jcr, jcr->db, &mr)) {
274 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
275 mr.VolumeName, db_strerror(jcr->db));
276 set_jcr_job_status(jcr, JS_ErrorTerminated);
279 update_bootstrap_file(jcr);
281 switch (jcr->JobStatus) {
283 if (jcr->Errors || jcr->SDErrors) {
284 term_msg = _("Backup OK -- with warnings");
286 term_msg = _("Backup OK");
290 case JS_ErrorTerminated:
291 term_msg = _("*** Backup Error ***");
292 msg_type = M_ERROR; /* Generate error message */
293 if (jcr->store_bsock) {
294 jcr->store_bsock->signal(BNET_TERMINATE);
295 if (jcr->SD_msg_chan) {
296 pthread_cancel(jcr->SD_msg_chan);
301 term_msg = _("Backup Canceled");
302 if (jcr->store_bsock) {
303 jcr->store_bsock->signal(BNET_TERMINATE);
304 if (jcr->SD_msg_chan) {
305 pthread_cancel(jcr->SD_msg_chan);
310 term_msg = term_code;
311 sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
314 bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
315 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
316 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
317 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
321 kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
323 if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
325 * Note, if the job has erred, most likely it did not write any
326 * tape, so suppress this "error" message since in that case
327 * it is normal. Or look at it the other way, only for a
328 * normal exit should we complain about this error.
330 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
331 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
333 jcr->VolumeName[0] = 0; /* none */
336 if (jcr->ReadBytes == 0) {
337 bstrncpy(compress, "None", sizeof(compress));
339 compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
340 if (compression < 0.5) {
341 bstrncpy(compress, "None", sizeof(compress));
343 bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
346 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
347 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
349 // bmicrosleep(15, 0); /* for debugging SIGHUP */
351 Jmsg(jcr, msg_type, 0, _("Bacula %s %s (%s): %s\n"
352 " Build OS: %s %s %s\n"
355 " Backup Level: %s%s\n"
356 " Client: \"%s\" %s\n"
357 " FileSet: \"%s\" %s\n"
358 " Pool: \"%s\" (From %s)\n"
359 " Catalog: \"%s\" (From %s)\n"
360 " Storage: \"%s\" (From %s)\n"
361 " Scheduled time: %s\n"
364 " Elapsed time: %s\n"
366 " FD Files Written: %s\n"
367 " SD Files Written: %s\n"
368 " FD Bytes Written: %s (%sB)\n"
369 " SD Bytes Written: %s (%sB)\n"
371 " Software Compression: %s\n"
375 " Volume name(s): %s\n"
376 " Volume Session Id: %d\n"
377 " Volume Session Time: %d\n"
378 " Last Volume Bytes: %s (%sB)\n"
379 " Non-fatal FD errors: %d\n"
381 " FD termination status: %s\n"
382 " SD termination status: %s\n"
383 " Termination: %s\n\n"),
384 my_name, VERSION, LSMDATE, edt,
385 HOST_OS, DISTNAME, DISTVER,
388 level_to_str(jcr->JobLevel), jcr->since,
389 jcr->client->name(), cr.Uname,
390 jcr->fileset->name(), jcr->FSCreateTime,
391 jcr->pool->name(), jcr->pool_source,
392 jcr->catalog->name(), jcr->catalog_source,
393 jcr->wstore->name(), jcr->wstore_source,
397 edit_utime(RunTime, elapsed, sizeof(elapsed)),
399 edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
400 edit_uint64_with_commas(jcr->SDJobFiles, ec2),
401 edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
402 edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
403 edit_uint64_with_commas(jcr->SDJobBytes, ec5),
404 edit_uint64_with_suffix(jcr->SDJobBytes, ec6),
407 jcr->VSS?_("yes"):_("no"),
408 jcr->Encrypt?_("yes"):_("no"),
409 jcr->accurate?_("yes"):_("no"),
413 edit_uint64_with_commas(mr.VolBytes, ec7),
414 edit_uint64_with_suffix(mr.VolBytes, ec8),
421 Dmsg0(100, "Leave vbackup_cleanup()\n");
425 * This callback routine is responsible for inserting the
426 * items it gets into the bootstrap structure. For each JobId selected
427 * this routine is called once for each file. We do not allow
428 * duplicate filenames, but instead keep the info from the most
429 * recent file entered (i.e. the JobIds are assumed to be sorted)
431 * See uar_sel_files in sql_cmds.c for query that calls us.
432 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
433 * row[3]=JobId row[4]=LStat
435 int insert_bootstrap_handler(void *ctx, int num_fields, char **row)
439 RBSR *bsr = (RBSR *)ctx;
441 JobId = str_to_int64(row[3]);
442 FileIndex = str_to_int64(row[2]);
443 add_findex(bsr, JobId, FileIndex);
448 static bool create_bootstrap_file(JCR *jcr, POOLMEM *jobids)
453 memset(&rx, 0, sizeof(rx));
455 ua = new_ua_context(jcr);
458 #define new_get_file_list
459 #ifdef new_get_file_list
460 if (!db_get_file_list(jcr, ua->db, jobids, insert_bootstrap_handler, (void *)rx.bsr)) {
461 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
465 JobId_t JobId, last_JobId = 0;
466 rx.query = get_pool_memory(PM_MESSAGE);
467 for (p=rx.JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
470 if (JobId == last_JobId) {
471 continue; /* eliminate duplicate JobIds */
475 * Find files for this JobId and insert them in the tree
477 Mmsg(rx.query, uar_sel_files, edit_int64(JobId, ed1));
478 Dmsg1(000, "uar_sel_files=%s\n", rx.query);
479 if (!db_sql_query(ua->db, rx.query, insert_bootstrap_handler, (void *)rx.bsr)) {
480 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
482 free_pool_memory(rx.query);
487 complete_bsr(ua, rx.bsr);
488 Dmsg0(000, "Print bsr\n");
489 print_bsr(ua, rx.bsr);
491 jcr->ExpectedFiles = write_bsr_file(ua, rx);
492 Dmsg1(000, "Found %d files to consolidate.\n", jcr->ExpectedFiles);
493 if (jcr->ExpectedFiles == 0) {