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]=MarkId 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 /* For incomplete Jobs, we add our own id */
246 if (jcr->incomplete) {
247 edit_int64(jcr->JobId, ed1);
250 if (!jcr->accurate || job_canceled(jcr)) {
253 /* In base level, no previous job is used */
254 if (jcr->is_JobLevel(L_BASE)) {
258 if (jcr->is_JobLevel(L_FULL)) {
259 /* On Full mode, if no previous base job, no accurate things */
260 if (!get_base_jobids(jcr, &jobids)) {
264 Jmsg(jcr, M_INFO, 0, _("Using BaseJobId(s): %s\n"), jobids.list);
267 /* For Incr/Diff level, we search for older jobs */
268 db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, &jobids);
270 /* We are in Incr/Diff, but no Full to build the accurate list... */
271 if (jobids.count == 0) {
272 Jmsg(jcr, M_FATAL, 0, _("Cannot find previous jobids.\n"));
273 return false; /* fail */
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 Jmsg(jcr, M_INFO, 0, _("Start Backup JobId %s, Job=%s\n"),
336 edit_uint64(jcr->JobId, ed1), jcr->Job);
338 jcr->setJobStatus(JS_Running);
339 Dmsg2(100, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
340 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
341 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
345 /* For incomplete Jobs, we add our own id */
346 if (jcr->incomplete) {
347 edit_int64(jcr->JobId, ed1);
348 Mmsg(buf, "SELECT max(FileIndex) FROM File WHERE JobId=%s", ed1);
349 if (!db_sql_query(jcr->db, buf.c_str(), db_int64_handler, &job)) {
350 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
353 jcr->JobFiles = job.value;
354 Dmsg1(100, "==== FI=%ld\n", jcr->JobFiles);
355 Mmsg(buf, "SELECT VolSessionId FROM Job WHERE JobId=%s", ed1);
356 if (!db_sql_query(jcr->db, buf.c_str(), db_int64_handler, &job)) {
357 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
360 jcr->VolSessionId = job.value;
361 Mmsg(buf, "SELECT VolSessionTime FROM Job WHERE JobId=%s", ed1);
362 if (!db_sql_query(jcr->db, buf.c_str(), db_int64_handler, &job)) {
363 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
366 jcr->VolSessionTime = job.value;
367 Dmsg4(100, "JobId=%s JobFiles=%ld VolSessionId=%ld VolSessionTime=%ld\n", ed1,
368 jcr->JobFiles, jcr->VolSessionId, jcr->VolSessionTime);
372 * Open a message channel connection with the Storage
373 * daemon. This is to let him know that our client
374 * will be contacting him for a backup session.
377 Dmsg0(110, "Open connection with storage daemon\n");
378 jcr->setJobStatus(JS_WaitSD);
380 * Start conversation with Storage daemon
382 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
386 * Now start a job with the Storage daemon
388 if (!start_storage_daemon_job(jcr, NULL, jcr->wstorage)) {
393 * Start the job prior to starting the message thread below
394 * to avoid two threads from using the BSOCK structure at
397 if (!jcr->store_bsock->fsend("run")) {
402 * Now start a Storage daemon message thread. Note,
403 * this thread is used to provide the catalog services
404 * for the backup job, including inserting the attributes
405 * into the catalog. See catalog_update() in catreq.c
407 if (!start_storage_daemon_message_thread(jcr)) {
410 Dmsg0(150, "Storage daemon connection OK\n");
412 jcr->setJobStatus(JS_WaitFD);
413 if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
417 jcr->setJobStatus(JS_Running);
418 fd = jcr->file_bsock;
420 if (!send_include_list(jcr)) {
424 if (!send_exclude_list(jcr)) {
428 if (!send_level_command(jcr)) {
433 * send Storage daemon address to the File daemon
436 if (store->SDDport == 0) {
437 store->SDDport = store->SDport;
440 /* TLS Requirement */
441 if (store->tls_enable) {
442 if (store->tls_require) {
443 tls_need = BNET_TLS_REQUIRED;
445 tls_need = BNET_TLS_OK;
449 fd->fsend(storaddr, store->address, store->SDDport, tls_need);
450 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
454 if (!send_runscripts_commands(jcr)) {
459 * We re-update the job start record so that the start
460 * time is set after the run before job. This avoids
461 * that any files created by the run before job will
462 * be saved twice. They will be backed up in the current
463 * job, but not in the next one unless they are changed.
464 * Without this, they will be backed up in this job and
465 * in the next job run because in that case, their date
466 * is after the start of this run.
468 jcr->start_time = time(NULL);
469 jcr->jr.StartTime = jcr->start_time;
470 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
471 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
475 * If backup is in accurate mode, we send the list of
478 if (!send_accurate_current_files(jcr)) {
479 goto bail_out; /* error */
482 /* Send backup command */
483 fd->fsend(backupcmd, jcr->JobFiles);
484 Dmsg1(100, ">filed: %s", fd->msg);
485 if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) {
489 /* Pickup Job termination data */
490 stat = wait_for_job_termination(jcr);
491 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
493 if (jcr->HasBase && !db_commit_base_file_attributes_record(jcr, jcr->db)) {
494 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
497 if (stat == JS_Terminated) {
498 backup_cleanup(jcr, stat);
503 /* Come here only after starting SD thread */
505 jcr->setJobStatus(JS_ErrorTerminated);
506 Dmsg1(400, "wait for sd. use=%d\n", jcr->use_count());
508 wait_for_job_termination(jcr, FDConnectTimeout);
509 Dmsg1(400, "after wait for sd. use=%d\n", jcr->use_count());
515 * Here we wait for the File daemon to signal termination,
516 * then we wait for the Storage daemon. When both
517 * are done, we return the job status.
518 * Also used by restore.c
520 int wait_for_job_termination(JCR *jcr, int timeout)
523 BSOCK *fd = jcr->file_bsock;
525 uint32_t JobFiles, JobErrors;
526 uint32_t JobWarnings = 0;
527 uint64_t ReadBytes = 0;
528 uint64_t JobBytes = 0;
533 jcr->setJobStatus(JS_Running);
537 tid = start_bsock_timer(fd, timeout); /* TODO: New timeout directive??? */
539 /* Wait for Client to terminate */
540 while ((n = bget_dirmsg(fd)) >= 0) {
542 (sscanf(fd->msg, EndJob, &jcr->FDJobStatus, &JobFiles,
543 &ReadBytes, &JobBytes, &JobErrors, &VSS, &Encrypt) == 7 ||
544 sscanf(fd->msg, OldEndJob, &jcr->FDJobStatus, &JobFiles,
545 &ReadBytes, &JobBytes, &JobErrors) == 5)) {
547 jcr->setJobStatus(jcr->FDJobStatus);
548 Dmsg1(100, "FDStatus=%c\n", (char)jcr->JobStatus);
550 Jmsg(jcr, M_WARNING, 0, _("Unexpected Client Job message: %s\n"),
553 if (job_canceled(jcr)) {
558 stop_bsock_timer(tid);
561 if (is_bnet_error(fd)) {
563 Jmsg(jcr, M_FATAL, 0, _("Network error with FD during %s: ERR=%s\n"),
564 job_type_to_str(jcr->getJobType()), fd->bstrerror());
565 while (i++ < 10 && jcr->job->RescheduleIncompleteJobs && jcr->is_canceled()) {
570 fd->signal(BNET_TERMINATE); /* tell Client we are terminating */
574 * Force cancel in SD if failing, but not for Incomplete jobs
575 * so that we let the SD despool.
577 Dmsg5(100, "cancel=%d fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", jcr->is_canceled(), fd_ok, jcr->FDJobStatus,
578 jcr->JobStatus, jcr->SDJobStatus);
579 if (jcr->is_canceled() || (!jcr->job->RescheduleIncompleteJobs && !fd_ok)) {
580 Dmsg4(100, "fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", fd_ok, jcr->FDJobStatus,
581 jcr->JobStatus, jcr->SDJobStatus);
582 cancel_storage_daemon_job(jcr);
585 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
586 wait_for_storage_daemon_termination(jcr);
588 /* Return values from FD */
590 jcr->JobFiles = JobFiles;
591 jcr->JobErrors += JobErrors; /* Keep total errors */
592 jcr->ReadBytes = ReadBytes;
593 jcr->JobBytes = JobBytes;
594 jcr->JobWarnings = JobWarnings;
596 jcr->Encrypt = Encrypt;
598 Jmsg(jcr, M_FATAL, 0, _("No Job status returned from FD.\n"));
601 // Dmsg4(100, "fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", fd_ok, jcr->FDJobStatus,
602 // jcr->JobStatus, jcr->SDJobStatus);
604 /* Return the first error status we find Dir, FD, or SD */
605 if (!fd_ok || is_bnet_error(fd)) { /* if fd not set, that use !fd_ok */
606 jcr->FDJobStatus = JS_ErrorTerminated;
608 if (jcr->JobStatus != JS_Terminated) {
609 return jcr->JobStatus;
611 if (jcr->FDJobStatus != JS_Terminated) {
612 return jcr->FDJobStatus;
614 return jcr->SDJobStatus;
618 * Release resources allocated during backup.
620 void backup_cleanup(JCR *jcr, int TermCode)
622 char sdt[50], edt[50], schedt[50];
623 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], compress[50];
624 char ec6[30], ec7[30], ec8[30], elapsed[50];
625 char term_code[100], fd_term_msg[100], sd_term_msg[100];
626 const char *term_msg;
627 int msg_type = M_INFO;
630 double kbps, compression;
634 if (jcr->is_JobLevel(L_VIRTUAL_FULL)) {
635 vbackup_cleanup(jcr, TermCode);
639 Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
640 memset(&mr, 0, sizeof(mr));
641 memset(&cr, 0, sizeof(cr));
644 if (jcr->getJobStatus() == JS_Terminated &&
645 (jcr->JobErrors || jcr->SDErrors || jcr->JobWarnings)) {
646 TermCode = JS_Warnings;
650 update_job_end(jcr, TermCode);
652 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
653 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
654 db_strerror(jcr->db));
655 jcr->setJobStatus(JS_ErrorTerminated);
658 bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
659 if (!db_get_client_record(jcr, jcr->db, &cr)) {
660 Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
661 db_strerror(jcr->db));
664 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
665 if (!db_get_media_record(jcr, jcr->db, &mr)) {
666 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
667 mr.VolumeName, db_strerror(jcr->db));
668 jcr->setJobStatus(JS_ErrorTerminated);
671 update_bootstrap_file(jcr);
673 switch (jcr->JobStatus) {
675 if (jcr->JobErrors || jcr->SDErrors) {
676 term_msg = _("Backup OK -- with warnings");
678 term_msg = _("Backup OK");
682 term_msg = _("Backup failed -- incomplete");
685 term_msg = _("Backup OK -- with warnings");
688 case JS_ErrorTerminated:
689 term_msg = _("*** Backup Error ***");
690 msg_type = M_ERROR; /* Generate error message */
691 if (jcr->store_bsock) {
692 jcr->store_bsock->signal(BNET_TERMINATE);
693 if (jcr->SD_msg_chan) {
694 pthread_cancel(jcr->SD_msg_chan);
699 term_msg = _("Backup Canceled");
700 if (jcr->store_bsock) {
701 jcr->store_bsock->signal(BNET_TERMINATE);
702 if (jcr->SD_msg_chan) {
703 pthread_cancel(jcr->SD_msg_chan);
708 term_msg = term_code;
709 sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
712 bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
713 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
714 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
715 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
719 kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
721 if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
723 * Note, if the job has erred, most likely it did not write any
724 * tape, so suppress this "error" message since in that case
725 * it is normal. Or look at it the other way, only for a
726 * normal exit should we complain about this error.
728 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
729 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
731 jcr->VolumeName[0] = 0; /* none */
734 if (jcr->ReadBytes == 0) {
735 bstrncpy(compress, "None", sizeof(compress));
737 compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
738 if (compression < 0.5) {
739 bstrncpy(compress, "None", sizeof(compress));
741 bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
744 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
745 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
748 Mmsg(base_info, " Base files/Used files: %lld/%lld (%.2f%%)\n",
750 jcr->nb_base_files_used,
751 jcr->nb_base_files_used*100.0/jcr->nb_base_files);
753 // bmicrosleep(15, 0); /* for debugging SIGHUP */
755 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
756 " Build OS: %s %s %s\n"
759 " Backup Level: %s%s\n"
760 " Client: \"%s\" %s\n"
761 " FileSet: \"%s\" %s\n"
762 " Pool: \"%s\" (From %s)\n"
763 " Catalog: \"%s\" (From %s)\n"
764 " Storage: \"%s\" (From %s)\n"
765 " Scheduled time: %s\n"
768 " Elapsed time: %s\n"
770 " FD Files Written: %s\n"
771 " SD Files Written: %s\n"
772 " FD Bytes Written: %s (%sB)\n"
773 " SD Bytes Written: %s (%sB)\n"
775 " Software Compression: %s\n"
776 "%s" /* Basefile info */
780 " Volume name(s): %s\n"
781 " Volume Session Id: %d\n"
782 " Volume Session Time: %d\n"
783 " Last Volume Bytes: %s (%sB)\n"
784 " Non-fatal FD errors: %d\n"
786 " FD termination status: %s\n"
787 " SD termination status: %s\n"
788 " Termination: %s\n\n"),
789 BACULA, my_name, VERSION, LSMDATE,
790 HOST_OS, DISTNAME, DISTVER,
793 level_to_str(jcr->getJobLevel()), jcr->since,
794 jcr->client->name(), cr.Uname,
795 jcr->fileset->name(), jcr->FSCreateTime,
796 jcr->pool->name(), jcr->pool_source,
797 jcr->catalog->name(), jcr->catalog_source,
798 jcr->wstore->name(), jcr->wstore_source,
802 edit_utime(RunTime, elapsed, sizeof(elapsed)),
804 edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
805 edit_uint64_with_commas(jcr->SDJobFiles, ec2),
806 edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
807 edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
808 edit_uint64_with_commas(jcr->SDJobBytes, ec5),
809 edit_uint64_with_suffix(jcr->SDJobBytes, ec6),
813 jcr->VSS?_("yes"):_("no"),
814 jcr->Encrypt?_("yes"):_("no"),
815 jcr->accurate?_("yes"):_("no"),
819 edit_uint64_with_commas(mr.VolBytes, ec7),
820 edit_uint64_with_suffix(mr.VolBytes, ec8),
827 Dmsg0(100, "Leave backup_cleanup()\n");
830 void update_bootstrap_file(JCR *jcr)
832 /* Now update the bootstrap file if any */
833 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes &&
834 jcr->job->WriteBootstrap) {
838 POOLMEM *fname = get_pool_memory(PM_FNAME);
839 fname = edit_job_codes(jcr, fname, jcr->job->WriteBootstrap, "");
841 VOL_PARAMS *VolParams = NULL;
843 char edt[50], ed1[50], ed2[50];
847 bpipe = open_bpipe(fname+1, 0, "w"); /* skip first char "|" */
848 fd = bpipe ? bpipe->wfd : NULL;
850 /* ***FIXME*** handle BASE */
851 fd = fopen(fname, jcr->is_JobLevel(L_FULL)?"w+b":"a+b");
854 VolCount = db_get_job_volume_parameters(jcr, jcr->db, jcr->JobId,
857 Jmsg(jcr, M_ERROR, 0, _("Could not get Job Volume Parameters to "
858 "update Bootstrap file. ERR=%s\n"), db_strerror(jcr->db));
859 if (jcr->SDJobFiles != 0) {
860 jcr->setJobStatus(JS_ErrorTerminated);
864 /* Start output with when and who wrote it */
865 bstrftimes(edt, sizeof(edt), time(NULL));
866 fprintf(fd, "# %s - %s - %s%s\n", edt, jcr->jr.Job,
867 level_to_str(jcr->getJobLevel()), jcr->since);
868 for (int i=0; i < VolCount; i++) {
869 /* Write the record */
870 fprintf(fd, "Volume=\"%s\"\n", VolParams[i].VolumeName);
871 fprintf(fd, "MediaType=\"%s\"\n", VolParams[i].MediaType);
872 if (VolParams[i].Slot > 0) {
873 fprintf(fd, "Slot=%d\n", VolParams[i].Slot);
875 fprintf(fd, "VolSessionId=%u\n", jcr->VolSessionId);
876 fprintf(fd, "VolSessionTime=%u\n", jcr->VolSessionTime);
877 fprintf(fd, "VolAddr=%s-%s\n",
878 edit_uint64(VolParams[i].StartAddr, ed1),
879 edit_uint64(VolParams[i].EndAddr, ed2));
880 fprintf(fd, "FileIndex=%d-%d\n", VolParams[i].FirstIndex,
881 VolParams[i].LastIndex);
893 Jmsg(jcr, M_ERROR, 0, _("Could not open WriteBootstrap file:\n"
894 "%s: ERR=%s\n"), fname, be.bstrerror());
895 jcr->setJobStatus(JS_ErrorTerminated);
897 free_pool_memory(fname);