2 Bacula® - The Network Backup Solution
4 Copyright (C) 2000-2011 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 three of the GNU Affero 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 Affero 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 -- backup.c -- responsible for doing backup jobs
32 * Kern Sibbald, March MM
34 * Basic tasks done here:
35 * Open DB and create records for this job.
36 * Open Message Channel with Storage daemon to tell him a job will be starting.
37 * Open connection with File daemon and pass him commands
39 * When the File daemon finishes the job, update the DB.
47 /* Commands sent to File daemon */
48 static char backupcmd[] = "backup FileIndex=%ld\n";
49 static char storaddr[] = "storage address=%s port=%d ssl=%d\n";
51 /* Responses received from File daemon */
52 static char OKbackup[] = "2000 OK backup\n";
53 static char OKstore[] = "2000 OK storage\n";
54 static char EndJob[] = "2800 End Job TermCode=%d JobFiles=%u "
55 "ReadBytes=%llu JobBytes=%llu Errors=%u "
56 "VSS=%d Encrypt=%d\n";
57 /* Pre 1.39.29 (04Dec06) EndJob */
58 static char OldEndJob[] = "2800 End Job TermCode=%d JobFiles=%u "
59 "ReadBytes=%llu JobBytes=%llu Errors=%u\n";
61 * Called here before the job is run to do the job
64 bool do_backup_init(JCR *jcr)
67 if (jcr->is_JobLevel(L_VIRTUAL_FULL)) {
68 return do_vbackup_init(jcr);
70 free_rstorage(jcr); /* we don't read so release */
72 if (!get_or_create_fileset_record(jcr)) {
77 * Get definitive Job level and since time
79 get_level_since_time(jcr, jcr->since, sizeof(jcr->since));
81 apply_pool_overrides(jcr);
83 if (!allow_duplicate_job(jcr)) {
87 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->name());
88 if (jcr->jr.PoolId == 0) {
92 /* If pool storage specified, use it instead of job storage */
93 copy_wstorage(jcr, jcr->pool->storage, _("Pool resource"));
96 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Job or Pool.\n"));
100 create_clones(jcr); /* run any clone jobs */
105 /* Take all base jobs from job resource and find the
108 static bool get_base_jobids(JCR *jcr, db_list_ctx *jobids)
115 if (!jcr->job->base) {
116 return false; /* no base job, stop accurate */
119 memset(&jr, 0, sizeof(JOB_DBR));
120 jr.StartTime = jcr->jr.StartTime;
122 foreach_alist(job, jcr->job->base) {
123 bstrncpy(jr.Name, job->name(), sizeof(jr.Name));
124 db_get_base_jobid(jcr, jcr->db, &jr, &id);
128 pm_strcat(jobids->list, ",");
130 pm_strcat(jobids->list, edit_uint64(id, str_jobid));
135 return jobids->count > 0;
139 * Foreach files in currrent list, send "/path/fname\0LStat\0MD5\0Delta" to FD
140 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
141 * row[3]=JobId row[4]=LStat row[5]=DeltaSeq row[6]=MD5
143 static int accurate_list_handler(void *ctx, int num_fields, char **row)
145 JCR *jcr = (JCR *)ctx;
147 if (job_canceled(jcr)) {
151 if (row[2][0] == '0') { /* discard when file_index == 0 */
155 /* sending with checksum */
156 if (jcr->use_accurate_chksum
158 && row[6][0] /* skip checksum = '0' */
161 jcr->file_bsock->fsend("%s%s%c%s%c%s%c%s",
162 row[0], row[1], 0, row[4], 0, row[6], 0, row[5]);
164 jcr->file_bsock->fsend("%s%s%c%s%c%c%s",
165 row[0], row[1], 0, row[4], 0, 0, row[5]);
170 /* In this procedure, we check if the current fileset is using checksum
171 * FileSet-> Include-> Options-> Accurate/Verify/BaseJob=checksum
172 * This procedure uses jcr->HasBase, so it must be call after the initialization
174 static bool is_checksum_needed_by_fileset(JCR *jcr)
180 bool have_basejob_option=false;
181 if (!jcr->job || !jcr->job->fileset) {
185 f = jcr->job->fileset;
187 for (int i=0; i < f->num_includes; i++) { /* Parse all Include {} */
188 inc = f->include_items[i];
190 for (int j=0; j < inc->num_opts; j++) { /* Parse all Options {} */
191 fopts = inc->opts_list[j];
193 for (char *k=fopts->opts; *k ; k++) { /* Try to find one request */
195 case 'V': /* verify */
196 in_block = (jcr->getJobType() == JT_VERIFY); /* not used now */
198 case 'J': /* Basejob keyword */
199 have_basejob_option = in_block = jcr->HasBase;
201 case 'C': /* Accurate keyword */
202 in_block = !jcr->is_JobLevel(L_FULL);
204 case ':': /* End of keyword */
210 Dmsg0(50, "Checksum will be sent to FD\n");
221 /* By default for BaseJobs, we send the checksum */
222 if (!have_basejob_option && jcr->HasBase) {
226 Dmsg0(50, "Checksum will be sent to FD\n");
231 * Send current file list to FD
232 * DIR -> FD : accurate files=xxxx
233 * DIR -> FD : /path/to/file\0Lstat\0MD5\0Delta
234 * DIR -> FD : /path/to/dir/\0Lstat\0MD5\0Delta
238 bool send_accurate_current_files(JCR *jcr)
245 /* In base level, no previous job is used and no restart incomplete jobs */
246 if (jcr->is_canceled() || jcr->is_JobLevel(L_BASE)) {
249 if (!jcr->accurate && !jcr->rerunning) {
253 if (jcr->is_JobLevel(L_FULL)) {
254 /* On Full mode, if no previous base job, no accurate things */
255 if (get_base_jobids(jcr, &jobids)) {
257 Jmsg(jcr, M_INFO, 0, _("Using BaseJobId(s): %s\n"), jobids.list);
258 } else if (!jcr->rerunning) {
262 /* For Incr/Diff level, we search for older jobs */
263 db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, &jobids);
265 /* We are in Incr/Diff, but no Full to build the accurate list... */
266 if (jobids.count == 0) {
267 Jmsg(jcr, M_FATAL, 0, _("Cannot find previous jobids.\n"));
268 return false; /* fail */
272 /* For incomplete Jobs, we add our own id */
273 if (jcr->rerunning) {
274 edit_int64(jcr->JobId, ed1);
278 /* Don't send and store the checksum if fileset doesn't require it */
279 jcr->use_accurate_chksum = is_checksum_needed_by_fileset(jcr);
281 if (jcr->JobId) { /* display the message only for real jobs */
282 Jmsg(jcr, M_INFO, 0, _("Sending Accurate information.\n"));
285 /* to be able to allocate the right size for htable */
286 Mmsg(buf, "SELECT sum(JobFiles) FROM Job WHERE JobId IN (%s)", jobids.list);
287 db_sql_query(jcr->db, buf.c_str(), db_list_handler, &nb);
288 Dmsg2(200, "jobids=%s nb=%s\n", jobids.list, nb.list);
289 jcr->file_bsock->fsend("accurate files=%s\n", nb.list);
291 if (!db_open_batch_connexion(jcr, jcr->db)) {
292 Jmsg0(jcr, M_FATAL, 0, "Can't get batch sql connexion");
293 return false; /* Fail */
297 jcr->nb_base_files = str_to_int64(nb.list);
298 db_create_base_file_list(jcr, jcr->db, jobids.list);
299 db_get_base_file_list(jcr, jcr->db, jcr->use_accurate_chksum,
300 accurate_list_handler, (void *)jcr);
303 db_get_file_list(jcr, jcr->db_batch,
304 jobids.list, jcr->use_accurate_chksum, false /* no delta */,
305 accurate_list_handler, (void *)jcr);
308 /* TODO: close the batch connection ? (can be used very soon) */
310 jcr->file_bsock->signal(BNET_EOD);
315 * Do a backup of the specified FileSet
317 * Returns: false on failure
320 bool do_backup(JCR *jcr)
323 int tls_need = BNET_TLS_NONE;
330 if (jcr->is_JobLevel(L_VIRTUAL_FULL)) {
331 return do_vbackup(jcr);
334 /* Print Job Start message */
335 if (jcr->rerunning) {
336 Jmsg(jcr, M_INFO, 0, _("Restart Incomplete Backup JobId %s, Job=%s\n"),
337 edit_uint64(jcr->JobId, ed1), jcr->Job);
339 Jmsg(jcr, M_INFO, 0, _("Start Backup JobId %s, Job=%s\n"),
340 edit_uint64(jcr->JobId, ed1), jcr->Job);
343 jcr->setJobStatus(JS_Running);
344 Dmsg2(100, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
345 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
346 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
350 /* For incomplete Jobs, we add our own id */
351 if (jcr->rerunning) {
352 edit_int64(jcr->JobId, ed1);
353 Mmsg(buf, "SELECT max(FileIndex) FROM File WHERE JobId=%s", ed1);
354 if (db_sql_query(jcr->db, buf.c_str(), db_int64_handler, &job)) {
355 Jmsg(jcr, M_INFO, 0, _("Found %ld files from prior incomplete Job.\n"),
358 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
361 jcr->JobFiles = job.value;
362 Dmsg1(100, "==== FI=%ld\n", jcr->JobFiles);
363 Mmsg(buf, "SELECT VolSessionId FROM Job WHERE JobId=%s", ed1);
364 if (!db_sql_query(jcr->db, buf.c_str(), db_int64_handler, &job)) {
365 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
368 jcr->VolSessionId = job.value;
369 Mmsg(buf, "SELECT VolSessionTime FROM Job WHERE JobId=%s", ed1);
370 if (!db_sql_query(jcr->db, buf.c_str(), db_int64_handler, &job)) {
371 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
374 jcr->VolSessionTime = job.value;
375 Dmsg4(100, "JobId=%s JobFiles=%ld VolSessionId=%ld VolSessionTime=%ld\n", ed1,
376 jcr->JobFiles, jcr->VolSessionId, jcr->VolSessionTime);
380 * Open a message channel connection with the Storage
381 * daemon. This is to let him know that our client
382 * will be contacting him for a backup session.
385 Dmsg0(110, "Open connection with storage daemon\n");
386 jcr->setJobStatus(JS_WaitSD);
388 * Start conversation with Storage daemon
390 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
394 * Now start a job with the Storage daemon
396 if (!start_storage_daemon_job(jcr, NULL, jcr->wstorage)) {
401 * Start the job prior to starting the message thread below
402 * to avoid two threads from using the BSOCK structure at
405 if (!jcr->store_bsock->fsend("run")) {
410 * Now start a Storage daemon message thread. Note,
411 * this thread is used to provide the catalog services
412 * for the backup job, including inserting the attributes
413 * into the catalog. See catalog_update() in catreq.c
415 if (!start_storage_daemon_message_thread(jcr)) {
418 Dmsg0(150, "Storage daemon connection OK\n");
420 jcr->setJobStatus(JS_WaitFD);
421 if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
425 jcr->setJobStatus(JS_Running);
426 fd = jcr->file_bsock;
428 if (!send_include_list(jcr)) {
432 if (!send_exclude_list(jcr)) {
436 if (!send_level_command(jcr)) {
440 /* TODO: See priority with bandwidth parameter */
441 if (jcr->job->max_bandwidth > 0) {
442 jcr->max_bandwidth = jcr->job->max_bandwidth;
443 } else if (jcr->client->max_bandwidth > 0) {
444 jcr->max_bandwidth = jcr->client->max_bandwidth;
447 if (jcr->max_bandwidth > 0) {
448 send_bwlimit(jcr, jcr->Job); /* Old clients don't have this command */
452 * send Storage daemon address to the File daemon
455 if (store->SDDport == 0) {
456 store->SDDport = store->SDport;
459 /* TLS Requirement */
460 if (store->tls_enable) {
461 if (store->tls_require) {
462 tls_need = BNET_TLS_REQUIRED;
464 tls_need = BNET_TLS_OK;
468 fd->fsend(storaddr, store->address, store->SDDport, tls_need);
469 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
473 if (!send_runscripts_commands(jcr)) {
478 * We re-update the job start record so that the start
479 * time is set after the run before job. This avoids
480 * that any files created by the run before job will
481 * be saved twice. They will be backed up in the current
482 * job, but not in the next one unless they are changed.
483 * Without this, they will be backed up in this job and
484 * in the next job run because in that case, their date
485 * is after the start of this run.
487 jcr->start_time = time(NULL);
488 jcr->jr.StartTime = jcr->start_time;
489 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
490 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
494 * If backup is in accurate mode, we send the list of
497 if (!send_accurate_current_files(jcr)) {
498 goto bail_out; /* error */
501 /* Send backup command */
502 fd->fsend(backupcmd, jcr->JobFiles);
503 Dmsg1(100, ">filed: %s", fd->msg);
504 if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) {
508 /* Pickup Job termination data */
509 stat = wait_for_job_termination(jcr);
510 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
512 if (jcr->HasBase && !db_commit_base_file_attributes_record(jcr, jcr->db)) {
513 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
516 if (stat == JS_Terminated) {
517 backup_cleanup(jcr, stat);
522 /* Come here only after starting SD thread */
524 jcr->setJobStatus(JS_ErrorTerminated);
525 Dmsg1(400, "wait for sd. use=%d\n", jcr->use_count());
527 wait_for_job_termination(jcr, FDConnectTimeout);
528 Dmsg1(400, "after wait for sd. use=%d\n", jcr->use_count());
534 * Here we wait for the File daemon to signal termination,
535 * then we wait for the Storage daemon. When both
536 * are done, we return the job status.
537 * Also used by restore.c
539 int wait_for_job_termination(JCR *jcr, int timeout)
542 BSOCK *fd = jcr->file_bsock;
544 uint32_t JobFiles, JobErrors;
545 uint32_t JobWarnings = 0;
546 uint64_t ReadBytes = 0;
547 uint64_t JobBytes = 0;
552 jcr->setJobStatus(JS_Running);
556 tid = start_bsock_timer(fd, timeout); /* TODO: New timeout directive??? */
558 /* Wait for Client to terminate */
559 while ((n = bget_dirmsg(fd)) >= 0) {
561 (sscanf(fd->msg, EndJob, &jcr->FDJobStatus, &JobFiles,
562 &ReadBytes, &JobBytes, &JobErrors, &VSS, &Encrypt) == 7 ||
563 sscanf(fd->msg, OldEndJob, &jcr->FDJobStatus, &JobFiles,
564 &ReadBytes, &JobBytes, &JobErrors) == 5)) {
566 jcr->setJobStatus(jcr->FDJobStatus);
567 Dmsg1(100, "FDStatus=%c\n", (char)jcr->JobStatus);
569 Jmsg(jcr, M_WARNING, 0, _("Unexpected Client Job message: %s\n"),
572 if (job_canceled(jcr)) {
577 stop_bsock_timer(tid);
580 if (is_bnet_error(fd)) {
582 Jmsg(jcr, M_FATAL, 0, _("Network error with FD during %s: ERR=%s\n"),
583 job_type_to_str(jcr->getJobType()), fd->bstrerror());
584 while (i++ < 10 && jcr->job->RescheduleIncompleteJobs && jcr->is_canceled()) {
589 fd->signal(BNET_TERMINATE); /* tell Client we are terminating */
593 * Force cancel in SD if failing, but not for Incomplete jobs
594 * so that we let the SD despool.
596 Dmsg5(100, "cancel=%d fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", jcr->is_canceled(), fd_ok, jcr->FDJobStatus,
597 jcr->JobStatus, jcr->SDJobStatus);
598 if (jcr->is_canceled() || (!jcr->job->RescheduleIncompleteJobs && !fd_ok)) {
599 Dmsg4(100, "fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", fd_ok, jcr->FDJobStatus,
600 jcr->JobStatus, jcr->SDJobStatus);
601 cancel_storage_daemon_job(jcr);
604 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
605 wait_for_storage_daemon_termination(jcr);
607 /* Return values from FD */
609 jcr->JobFiles = JobFiles;
610 jcr->JobErrors += JobErrors; /* Keep total errors */
611 jcr->ReadBytes = ReadBytes;
612 jcr->JobBytes = JobBytes;
613 jcr->JobWarnings = JobWarnings;
615 jcr->Encrypt = Encrypt;
617 Jmsg(jcr, M_FATAL, 0, _("No Job status returned from FD.\n"));
620 // Dmsg4(100, "fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", fd_ok, jcr->FDJobStatus,
621 // jcr->JobStatus, jcr->SDJobStatus);
623 /* Return the first error status we find Dir, FD, or SD */
624 if (!fd_ok || is_bnet_error(fd)) { /* if fd not set, that use !fd_ok */
625 jcr->FDJobStatus = JS_ErrorTerminated;
627 if (jcr->JobStatus != JS_Terminated) {
628 return jcr->JobStatus;
630 if (jcr->FDJobStatus != JS_Terminated) {
631 return jcr->FDJobStatus;
633 return jcr->SDJobStatus;
637 * Release resources allocated during backup.
639 void backup_cleanup(JCR *jcr, int TermCode)
641 char sdt[50], edt[50], schedt[50];
642 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], compress[50];
643 char ec6[30], ec7[30], ec8[30], elapsed[50];
644 char term_code[100], fd_term_msg[100], sd_term_msg[100];
645 const char *term_msg;
646 int msg_type = M_INFO;
649 double kbps, compression;
653 if (jcr->is_JobLevel(L_VIRTUAL_FULL)) {
654 vbackup_cleanup(jcr, TermCode);
658 Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
659 memset(&mr, 0, sizeof(mr));
660 memset(&cr, 0, sizeof(cr));
663 if (jcr->getJobStatus() == JS_Terminated &&
664 (jcr->JobErrors || jcr->SDErrors || jcr->JobWarnings)) {
665 TermCode = JS_Warnings;
669 update_job_end(jcr, TermCode);
671 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
672 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
673 db_strerror(jcr->db));
674 jcr->setJobStatus(JS_ErrorTerminated);
677 bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
678 if (!db_get_client_record(jcr, jcr->db, &cr)) {
679 Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
680 db_strerror(jcr->db));
683 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
684 if (!db_get_media_record(jcr, jcr->db, &mr)) {
685 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
686 mr.VolumeName, db_strerror(jcr->db));
687 jcr->setJobStatus(JS_ErrorTerminated);
690 update_bootstrap_file(jcr);
692 switch (jcr->JobStatus) {
694 if (jcr->JobErrors || jcr->SDErrors) {
695 term_msg = _("Backup OK -- with warnings");
697 term_msg = _("Backup OK");
701 term_msg = _("Backup failed -- incomplete");
704 term_msg = _("Backup OK -- with warnings");
707 case JS_ErrorTerminated:
708 term_msg = _("*** Backup Error ***");
709 msg_type = M_ERROR; /* Generate error message */
710 if (jcr->store_bsock) {
711 jcr->store_bsock->signal(BNET_TERMINATE);
712 if (jcr->SD_msg_chan) {
713 pthread_cancel(jcr->SD_msg_chan);
718 term_msg = _("Backup Canceled");
719 if (jcr->store_bsock) {
720 jcr->store_bsock->signal(BNET_TERMINATE);
721 if (jcr->SD_msg_chan) {
722 pthread_cancel(jcr->SD_msg_chan);
727 term_msg = term_code;
728 sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
731 bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
732 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
733 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
734 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
738 kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
740 if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
742 * Note, if the job has erred, most likely it did not write any
743 * tape, so suppress this "error" message since in that case
744 * it is normal. Or look at it the other way, only for a
745 * normal exit should we complain about this error.
747 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
748 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
750 jcr->VolumeName[0] = 0; /* none */
753 if (jcr->ReadBytes == 0) {
754 bstrncpy(compress, "None", sizeof(compress));
756 compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
757 if (compression < 0.5) {
758 bstrncpy(compress, "None", sizeof(compress));
760 bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
763 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
764 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
767 Mmsg(base_info, " Base files/Used files: %lld/%lld (%.2f%%)\n",
769 jcr->nb_base_files_used,
770 jcr->nb_base_files_used*100.0/jcr->nb_base_files);
772 // bmicrosleep(15, 0); /* for debugging SIGHUP */
774 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
775 " Build OS: %s %s %s\n"
778 " Backup Level: %s%s\n"
779 " Client: \"%s\" %s\n"
780 " FileSet: \"%s\" %s\n"
781 " Pool: \"%s\" (From %s)\n"
782 " Catalog: \"%s\" (From %s)\n"
783 " Storage: \"%s\" (From %s)\n"
784 " Scheduled time: %s\n"
787 " Elapsed time: %s\n"
789 " FD Files Written: %s\n"
790 " SD Files Written: %s\n"
791 " FD Bytes Written: %s (%sB)\n"
792 " SD Bytes Written: %s (%sB)\n"
794 " Software Compression: %s\n"
795 "%s" /* Basefile info */
799 " Volume name(s): %s\n"
800 " Volume Session Id: %d\n"
801 " Volume Session Time: %d\n"
802 " Last Volume Bytes: %s (%sB)\n"
803 " Non-fatal FD errors: %d\n"
805 " FD termination status: %s\n"
806 " SD termination status: %s\n"
807 " Termination: %s\n\n"),
808 BACULA, my_name, VERSION, LSMDATE,
809 HOST_OS, DISTNAME, DISTVER,
812 level_to_str(jcr->getJobLevel()), jcr->since,
813 jcr->client->name(), cr.Uname,
814 jcr->fileset->name(), jcr->FSCreateTime,
815 jcr->pool->name(), jcr->pool_source,
816 jcr->catalog->name(), jcr->catalog_source,
817 jcr->wstore->name(), jcr->wstore_source,
821 edit_utime(RunTime, elapsed, sizeof(elapsed)),
823 edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
824 edit_uint64_with_commas(jcr->SDJobFiles, ec2),
825 edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
826 edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
827 edit_uint64_with_commas(jcr->SDJobBytes, ec5),
828 edit_uint64_with_suffix(jcr->SDJobBytes, ec6),
832 jcr->VSS?_("yes"):_("no"),
833 jcr->Encrypt?_("yes"):_("no"),
834 jcr->accurate?_("yes"):_("no"),
838 edit_uint64_with_commas(mr.VolBytes, ec7),
839 edit_uint64_with_suffix(mr.VolBytes, ec8),
846 Dmsg0(100, "Leave backup_cleanup()\n");
849 void update_bootstrap_file(JCR *jcr)
851 /* Now update the bootstrap file if any */
852 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes &&
853 jcr->job->WriteBootstrap) {
857 POOLMEM *fname = get_pool_memory(PM_FNAME);
858 fname = edit_job_codes(jcr, fname, jcr->job->WriteBootstrap, "");
860 VOL_PARAMS *VolParams = NULL;
862 char edt[50], ed1[50], ed2[50];
866 bpipe = open_bpipe(fname+1, 0, "w"); /* skip first char "|" */
867 fd = bpipe ? bpipe->wfd : NULL;
869 /* ***FIXME*** handle BASE */
870 fd = fopen(fname, jcr->is_JobLevel(L_FULL)?"w+b":"a+b");
873 VolCount = db_get_job_volume_parameters(jcr, jcr->db, jcr->JobId,
876 Jmsg(jcr, M_ERROR, 0, _("Could not get Job Volume Parameters to "
877 "update Bootstrap file. ERR=%s\n"), db_strerror(jcr->db));
878 if (jcr->SDJobFiles != 0) {
879 jcr->setJobStatus(JS_ErrorTerminated);
883 /* Start output with when and who wrote it */
884 bstrftimes(edt, sizeof(edt), time(NULL));
885 fprintf(fd, "# %s - %s - %s%s\n", edt, jcr->jr.Job,
886 level_to_str(jcr->getJobLevel()), jcr->since);
887 for (int i=0; i < VolCount; i++) {
888 /* Write the record */
889 fprintf(fd, "Volume=\"%s\"\n", VolParams[i].VolumeName);
890 fprintf(fd, "MediaType=\"%s\"\n", VolParams[i].MediaType);
891 if (VolParams[i].Slot > 0) {
892 fprintf(fd, "Slot=%d\n", VolParams[i].Slot);
894 fprintf(fd, "VolSessionId=%u\n", jcr->VolSessionId);
895 fprintf(fd, "VolSessionTime=%u\n", jcr->VolSessionTime);
896 fprintf(fd, "VolAddr=%s-%s\n",
897 edit_uint64(VolParams[i].StartAddr, ed1),
898 edit_uint64(VolParams[i].EndAddr, ed2));
899 fprintf(fd, "FileIndex=%d-%d\n", VolParams[i].FirstIndex,
900 VolParams[i].LastIndex);
912 Jmsg(jcr, M_ERROR, 0, _("Could not open WriteBootstrap file:\n"
913 "%s: ERR=%s\n"), fname, be.bstrerror());
914 jcr->setJobStatus(JS_ErrorTerminated);
916 free_pool_memory(fname);