2 Bacula® - The Network Backup Solution
4 Copyright (C) 2000-2009 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.
48 /* Commands sent to File daemon */
49 static char backupcmd[] = "backup\n";
50 static char storaddr[] = "storage address=%s port=%d ssl=%d\n";
52 /* Responses received from File daemon */
53 static char OKbackup[] = "2000 OK backup\n";
54 static char OKstore[] = "2000 OK storage\n";
55 static char EndJob[] = "2800 End Job TermCode=%d JobFiles=%u "
56 "ReadBytes=%llu JobBytes=%llu Errors=%u "
57 "VSS=%d Encrypt=%d\n";
58 /* Pre 1.39.29 (04Dec06) EndJob */
59 static char OldEndJob[] = "2800 End Job TermCode=%d JobFiles=%u "
60 "ReadBytes=%llu JobBytes=%llu Errors=%u\n";
62 * Called here before the job is run to do the job
65 bool do_backup_init(JCR *jcr)
68 if (jcr->getJobLevel() == L_VIRTUAL_FULL) {
69 return do_vbackup_init(jcr);
71 free_rstorage(jcr); /* we don't read so release */
73 if (!get_or_create_fileset_record(jcr)) {
78 * Get definitive Job level and since time
80 get_level_since_time(jcr, jcr->since, sizeof(jcr->since));
82 apply_pool_overrides(jcr);
84 if (!allow_duplicate_job(jcr)) {
88 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->name());
89 if (jcr->jr.PoolId == 0) {
93 /* If pool storage specified, use it instead of job storage */
94 copy_wstorage(jcr, jcr->pool->storage, _("Pool resource"));
97 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Job or Pool.\n"));
101 create_clones(jcr); /* run any clone jobs */
106 /* Take all base jobs from job resource and find the
109 static bool get_base_jobids(JCR *jcr, db_list_ctx *jobids)
116 if (!jcr->job->base) {
117 return false; /* no base job, stop accurate */
120 memset(&jr, 0, sizeof(JOB_DBR));
121 jr.StartTime = jcr->jr.StartTime;
123 foreach_alist(job, jcr->job->base) {
124 bstrncpy(jr.Name, job->name(), sizeof(jr.Name));
125 db_get_base_jobid(jcr, jcr->db, &jr, &id);
129 pm_strcat(jobids->list, ",");
131 pm_strcat(jobids->list, edit_uint64(id, str_jobid));
136 return jobids->count > 0;
140 * Foreach files in currrent list, send "/path/fname\0LStat\0MD5\0Delta" to FD
141 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
142 * row[3]=JobId row[4]=LStat row[5]=MarkId row[6]=MD5
144 static int accurate_list_handler(void *ctx, int num_fields, char **row)
146 JCR *jcr = (JCR *)ctx;
148 if (job_canceled(jcr)) {
152 if (row[2][0] == '0') { /* discard when file_index == 0 */
156 /* sending with checksum */
157 if (jcr->use_accurate_chksum
159 && row[6][0] /* skip checksum = '0' */
162 jcr->file_bsock->fsend("%s%s%c%s%c%s%c%s",
163 row[0], row[1], 0, row[4], 0, row[6], 0, row[5]);
165 jcr->file_bsock->fsend("%s%s%c%s%c%c%s",
166 row[0], row[1], 0, row[4], 0, 0, row[5]);
171 /* In this procedure, we check if the current fileset is using checksum
172 * FileSet-> Include-> Options-> Accurate/Verify/BaseJob=checksum
173 * This procedure uses jcr->HasBase, so it must be call after the initialization
175 static bool is_checksum_needed_by_fileset(JCR *jcr)
181 bool have_basejob_option=false;
182 if (!jcr->job || !jcr->job->fileset) {
186 f = jcr->job->fileset;
188 for (int i=0; i < f->num_includes; i++) { /* Parse all Include {} */
189 inc = f->include_items[i];
191 for (int j=0; j < inc->num_opts; j++) { /* Parse all Options {} */
192 fopts = inc->opts_list[j];
194 for (char *k=fopts->opts; *k ; k++) { /* Try to find one request */
196 case 'V': /* verify */
197 in_block = (jcr->getJobType() == JT_VERIFY); /* not used now */
199 case 'J': /* Basejob keyword */
200 have_basejob_option = in_block = jcr->HasBase;
202 case 'C': /* Accurate keyword */
203 in_block = (jcr->getJobLevel() != L_FULL);
205 case ':': /* End of keyword */
211 Dmsg0(50, "Checksum will be sent to FD\n");
222 /* By default for BaseJobs, we send the checksum */
223 if (!have_basejob_option && jcr->HasBase) {
227 Dmsg0(50, "Checksum will be sent to FD\n");
232 * Send current file list to FD
233 * DIR -> FD : accurate files=xxxx
234 * DIR -> FD : /path/to/file\0Lstat\0MD5\0Delta
235 * DIR -> FD : /path/to/dir/\0Lstat\0MD5\0Delta
239 bool send_accurate_current_files(JCR *jcr)
246 if (!jcr->accurate || job_canceled(jcr)) {
249 /* In base level, no previous job is used */
250 if (jcr->getJobLevel() == L_BASE) {
254 if (jcr->getJobLevel() == L_FULL) {
255 /* On Full mode, if no previous base job, no accurate things */
256 if (!get_base_jobids(jcr, &jobids)) {
260 Jmsg(jcr, M_INFO, 0, _("Using BaseJobId(s): %s\n"), jobids.list);
263 /* For Incr/Diff level, we search for older jobs */
264 db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, &jobids);
266 /* We are in Incr/Diff, but no Full to build the accurate list... */
267 if (jobids.count == 0) {
269 Jmsg(jcr, M_FATAL, 0, _("Cannot find previous jobids.\n"));
274 /* Don't send and store the checksum if fileset doesn't require it */
275 jcr->use_accurate_chksum = is_checksum_needed_by_fileset(jcr);
277 if (jcr->JobId) { /* display the message only for real jobs */
278 Jmsg(jcr, M_INFO, 0, _("Sending Accurate information.\n"));
281 /* to be able to allocate the right size for htable */
282 Mmsg(buf, "SELECT sum(JobFiles) FROM Job WHERE JobId IN (%s)", jobids.list);
283 db_sql_query(jcr->db, buf.c_str(), db_list_handler, &nb);
284 Dmsg2(200, "jobids=%s nb=%s\n", jobids.list, nb.list);
285 jcr->file_bsock->fsend("accurate files=%s\n", nb.list);
287 if (!db_open_batch_connexion(jcr, jcr->db)) {
288 Jmsg0(jcr, M_FATAL, 0, "Can't get batch sql connexion");
293 jcr->nb_base_files = str_to_int64(nb.list);
294 db_create_base_file_list(jcr, jcr->db, jobids.list);
295 db_get_base_file_list(jcr, jcr->db, jcr->use_accurate_chksum,
296 accurate_list_handler, (void *)jcr);
299 db_get_file_list(jcr, jcr->db_batch,
300 jobids.list, jcr->use_accurate_chksum,
301 accurate_list_handler, (void *)jcr);
304 /* TODO: close the batch connexion ? (can be used very soon) */
306 jcr->file_bsock->signal(BNET_EOD);
313 * Do a backup of the specified FileSet
315 * Returns: false on failure
318 bool do_backup(JCR *jcr)
321 int tls_need = BNET_TLS_NONE;
326 if (jcr->getJobLevel() == L_VIRTUAL_FULL) {
327 return do_vbackup(jcr);
330 /* Print Job Start message */
331 Jmsg(jcr, M_INFO, 0, _("Start Backup JobId %s, Job=%s\n"),
332 edit_uint64(jcr->JobId, ed1), jcr->Job);
334 jcr->setJobStatus(JS_Running);
335 Dmsg2(100, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
336 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
337 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
342 * Open a message channel connection with the Storage
343 * daemon. This is to let him know that our client
344 * will be contacting him for a backup session.
347 Dmsg0(110, "Open connection with storage daemon\n");
348 jcr->setJobStatus(JS_WaitSD);
350 * Start conversation with Storage daemon
352 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
356 * Now start a job with the Storage daemon
358 if (!start_storage_daemon_job(jcr, NULL, jcr->wstorage)) {
363 * Start the job prior to starting the message thread below
364 * to avoid two threads from using the BSOCK structure at
367 if (!bnet_fsend(jcr->store_bsock, "run")) {
372 * Now start a Storage daemon message thread. Note,
373 * this thread is used to provide the catalog services
374 * for the backup job, including inserting the attributes
375 * into the catalog. See catalog_update() in catreq.c
377 if (!start_storage_daemon_message_thread(jcr)) {
380 Dmsg0(150, "Storage daemon connection OK\n");
382 jcr->setJobStatus(JS_WaitFD);
383 if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
387 jcr->setJobStatus(JS_Running);
388 fd = jcr->file_bsock;
390 if (!send_include_list(jcr)) {
394 if (!send_exclude_list(jcr)) {
398 if (!send_level_command(jcr)) {
403 * send Storage daemon address to the File daemon
406 if (store->SDDport == 0) {
407 store->SDDport = store->SDport;
410 /* TLS Requirement */
411 if (store->tls_enable) {
412 if (store->tls_require) {
413 tls_need = BNET_TLS_REQUIRED;
415 tls_need = BNET_TLS_OK;
419 fd->fsend(storaddr, store->address, store->SDDport, tls_need);
420 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
424 if (!send_runscripts_commands(jcr)) {
429 * We re-update the job start record so that the start
430 * time is set after the run before job. This avoids
431 * that any files created by the run before job will
432 * be saved twice. They will be backed up in the current
433 * job, but not in the next one unless they are changed.
434 * Without this, they will be backed up in this job and
435 * in the next job run because in that case, their date
436 * is after the start of this run.
438 jcr->start_time = time(NULL);
439 jcr->jr.StartTime = jcr->start_time;
440 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
441 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
445 * If backup is in accurate mode, we send the list of
448 if (!send_accurate_current_files(jcr)) {
452 /* Send backup command */
453 fd->fsend(backupcmd);
454 if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) {
458 /* Pickup Job termination data */
459 stat = wait_for_job_termination(jcr);
460 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
463 !db_commit_base_file_attributes_record(jcr, jcr->db))
465 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
468 if (stat == JS_Terminated) {
469 backup_cleanup(jcr, stat);
474 /* Come here only after starting SD thread */
476 jcr->setJobStatus(JS_ErrorTerminated);
477 Dmsg1(400, "wait for sd. use=%d\n", jcr->use_count());
479 wait_for_job_termination(jcr, FDConnectTimeout);
480 Dmsg1(400, "after wait for sd. use=%d\n", jcr->use_count());
486 * Here we wait for the File daemon to signal termination,
487 * then we wait for the Storage daemon. When both
488 * are done, we return the job status.
489 * Also used by restore.c
491 int wait_for_job_termination(JCR *jcr, int timeout)
494 BSOCK *fd = jcr->file_bsock;
496 uint32_t JobFiles, JobErrors;
497 uint32_t JobWarnings = 0;
498 uint64_t ReadBytes = 0;
499 uint64_t JobBytes = 0;
504 jcr->setJobStatus(JS_Running);
508 tid = start_bsock_timer(fd, timeout); /* TODO: New timeout directive??? */
510 /* Wait for Client to terminate */
511 while ((n = bget_dirmsg(fd)) >= 0) {
513 (sscanf(fd->msg, EndJob, &jcr->FDJobStatus, &JobFiles,
514 &ReadBytes, &JobBytes, &JobErrors, &VSS, &Encrypt) == 7 ||
515 sscanf(fd->msg, OldEndJob, &jcr->FDJobStatus, &JobFiles,
516 &ReadBytes, &JobBytes, &JobErrors) == 5)) {
518 jcr->setJobStatus(jcr->FDJobStatus);
519 Dmsg1(100, "FDStatus=%c\n", (char)jcr->JobStatus);
521 Jmsg(jcr, M_WARNING, 0, _("Unexpected Client Job message: %s\n"),
524 if (job_canceled(jcr)) {
529 stop_bsock_timer(tid);
532 if (is_bnet_error(fd)) {
533 Jmsg(jcr, M_FATAL, 0, _("Network error with FD during %s: ERR=%s\n"),
534 job_type_to_str(jcr->getJobType()), fd->bstrerror());
536 fd->signal(BNET_TERMINATE); /* tell Client we are terminating */
539 /* Force cancel in SD if failing */
540 if (job_canceled(jcr) || !fd_ok) {
541 cancel_storage_daemon_job(jcr);
544 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
545 wait_for_storage_daemon_termination(jcr);
547 /* Return values from FD */
549 jcr->JobFiles = JobFiles;
550 jcr->JobErrors += JobErrors; /* Keep total errors */
551 jcr->ReadBytes = ReadBytes;
552 jcr->JobBytes = JobBytes;
553 jcr->JobWarnings = JobWarnings;
555 jcr->Encrypt = Encrypt;
557 Jmsg(jcr, M_FATAL, 0, _("No Job status returned from FD.\n"));
560 // Dmsg4(100, "fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", fd_ok, jcr->FDJobStatus,
561 // jcr->JobStatus, jcr->SDJobStatus);
563 /* Return the first error status we find Dir, FD, or SD */
564 if (!fd_ok || is_bnet_error(fd)) { /* if fd not set, that use !fd_ok */
565 jcr->FDJobStatus = JS_ErrorTerminated;
567 if (jcr->JobStatus != JS_Terminated) {
568 return jcr->JobStatus;
570 if (jcr->FDJobStatus != JS_Terminated) {
571 return jcr->FDJobStatus;
573 return jcr->SDJobStatus;
577 * Release resources allocated during backup.
579 void backup_cleanup(JCR *jcr, int TermCode)
581 char sdt[50], edt[50], schedt[50];
582 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], compress[50];
583 char ec6[30], ec7[30], ec8[30], elapsed[50];
584 char term_code[100], fd_term_msg[100], sd_term_msg[100];
585 const char *term_msg;
586 int msg_type = M_INFO;
589 double kbps, compression;
593 if (jcr->getJobLevel() == L_VIRTUAL_FULL) {
594 vbackup_cleanup(jcr, TermCode);
598 Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
599 memset(&mr, 0, sizeof(mr));
600 memset(&cr, 0, sizeof(cr));
603 if (jcr->getJobStatus() == JS_Terminated &&
604 (jcr->JobErrors || jcr->SDErrors || jcr->JobWarnings)) {
605 TermCode = JS_Warnings;
609 update_job_end(jcr, TermCode);
611 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
612 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
613 db_strerror(jcr->db));
614 jcr->setJobStatus(JS_ErrorTerminated);
617 bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
618 if (!db_get_client_record(jcr, jcr->db, &cr)) {
619 Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
620 db_strerror(jcr->db));
623 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
624 if (!db_get_media_record(jcr, jcr->db, &mr)) {
625 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
626 mr.VolumeName, db_strerror(jcr->db));
627 jcr->setJobStatus(JS_ErrorTerminated);
630 update_bootstrap_file(jcr);
632 switch (jcr->JobStatus) {
634 if (jcr->JobErrors || jcr->SDErrors) {
635 term_msg = _("Backup OK -- with warnings");
637 term_msg = _("Backup OK");
641 term_msg = _("Backup OK -- with warnings");
644 case JS_ErrorTerminated:
645 term_msg = _("*** Backup Error ***");
646 msg_type = M_ERROR; /* Generate error message */
647 if (jcr->store_bsock) {
648 jcr->store_bsock->signal(BNET_TERMINATE);
649 if (jcr->SD_msg_chan) {
650 pthread_cancel(jcr->SD_msg_chan);
655 term_msg = _("Backup Canceled");
656 if (jcr->store_bsock) {
657 jcr->store_bsock->signal(BNET_TERMINATE);
658 if (jcr->SD_msg_chan) {
659 pthread_cancel(jcr->SD_msg_chan);
664 term_msg = term_code;
665 sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
668 bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
669 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
670 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
671 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
675 kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
677 if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
679 * Note, if the job has erred, most likely it did not write any
680 * tape, so suppress this "error" message since in that case
681 * it is normal. Or look at it the other way, only for a
682 * normal exit should we complain about this error.
684 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
685 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
687 jcr->VolumeName[0] = 0; /* none */
690 if (jcr->ReadBytes == 0) {
691 bstrncpy(compress, "None", sizeof(compress));
693 compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
694 if (compression < 0.5) {
695 bstrncpy(compress, "None", sizeof(compress));
697 bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
700 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
701 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
704 Mmsg(base_info, " Base files/Used files: %lld/%lld (%.2f%%)\n",
706 jcr->nb_base_files_used,
707 jcr->nb_base_files_used*100.0/jcr->nb_base_files);
709 // bmicrosleep(15, 0); /* for debugging SIGHUP */
711 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
712 " Build OS: %s %s %s\n"
715 " Backup Level: %s%s\n"
716 " Client: \"%s\" %s\n"
717 " FileSet: \"%s\" %s\n"
718 " Pool: \"%s\" (From %s)\n"
719 " Catalog: \"%s\" (From %s)\n"
720 " Storage: \"%s\" (From %s)\n"
721 " Scheduled time: %s\n"
724 " Elapsed time: %s\n"
726 " FD Files Written: %s\n"
727 " SD Files Written: %s\n"
728 " FD Bytes Written: %s (%sB)\n"
729 " SD Bytes Written: %s (%sB)\n"
731 " Software Compression: %s\n"
732 "%s" /* Basefile info */
736 " Volume name(s): %s\n"
737 " Volume Session Id: %d\n"
738 " Volume Session Time: %d\n"
739 " Last Volume Bytes: %s (%sB)\n"
740 " Non-fatal FD errors: %d\n"
742 " FD termination status: %s\n"
743 " SD termination status: %s\n"
744 " Termination: %s\n\n"),
745 BACULA, my_name, VERSION, LSMDATE,
746 HOST_OS, DISTNAME, DISTVER,
749 level_to_str(jcr->getJobLevel()), jcr->since,
750 jcr->client->name(), cr.Uname,
751 jcr->fileset->name(), jcr->FSCreateTime,
752 jcr->pool->name(), jcr->pool_source,
753 jcr->catalog->name(), jcr->catalog_source,
754 jcr->wstore->name(), jcr->wstore_source,
758 edit_utime(RunTime, elapsed, sizeof(elapsed)),
760 edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
761 edit_uint64_with_commas(jcr->SDJobFiles, ec2),
762 edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
763 edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
764 edit_uint64_with_commas(jcr->SDJobBytes, ec5),
765 edit_uint64_with_suffix(jcr->SDJobBytes, ec6),
769 jcr->VSS?_("yes"):_("no"),
770 jcr->Encrypt?_("yes"):_("no"),
771 jcr->accurate?_("yes"):_("no"),
775 edit_uint64_with_commas(mr.VolBytes, ec7),
776 edit_uint64_with_suffix(mr.VolBytes, ec8),
783 Dmsg0(100, "Leave backup_cleanup()\n");
786 void update_bootstrap_file(JCR *jcr)
788 /* Now update the bootstrap file if any */
789 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes &&
790 jcr->job->WriteBootstrap) {
794 POOLMEM *fname = get_pool_memory(PM_FNAME);
795 fname = edit_job_codes(jcr, fname, jcr->job->WriteBootstrap, "");
797 VOL_PARAMS *VolParams = NULL;
799 char edt[50], ed1[50], ed2[50];
803 bpipe = open_bpipe(fname+1, 0, "w"); /* skip first char "|" */
804 fd = bpipe ? bpipe->wfd : NULL;
806 /* ***FIXME*** handle BASE */
807 fd = fopen(fname, jcr->getJobLevel()==L_FULL?"w+b":"a+b");
810 VolCount = db_get_job_volume_parameters(jcr, jcr->db, jcr->JobId,
813 Jmsg(jcr, M_ERROR, 0, _("Could not get Job Volume Parameters to "
814 "update Bootstrap file. ERR=%s\n"), db_strerror(jcr->db));
815 if (jcr->SDJobFiles != 0) {
816 jcr->setJobStatus(JS_ErrorTerminated);
820 /* Start output with when and who wrote it */
821 bstrftimes(edt, sizeof(edt), time(NULL));
822 fprintf(fd, "# %s - %s - %s%s\n", edt, jcr->jr.Job,
823 level_to_str(jcr->getJobLevel()), jcr->since);
824 for (int i=0; i < VolCount; i++) {
825 /* Write the record */
826 fprintf(fd, "Volume=\"%s\"\n", VolParams[i].VolumeName);
827 fprintf(fd, "MediaType=\"%s\"\n", VolParams[i].MediaType);
828 if (VolParams[i].Slot > 0) {
829 fprintf(fd, "Slot=%d\n", VolParams[i].Slot);
831 fprintf(fd, "VolSessionId=%u\n", jcr->VolSessionId);
832 fprintf(fd, "VolSessionTime=%u\n", jcr->VolSessionTime);
833 fprintf(fd, "VolAddr=%s-%s\n",
834 edit_uint64(VolParams[i].StartAddr, ed1),
835 edit_uint64(VolParams[i].EndAddr, ed2));
836 fprintf(fd, "FileIndex=%d-%d\n", VolParams[i].FirstIndex,
837 VolParams[i].LastIndex);
849 Jmsg(jcr, M_ERROR, 0, _("Could not open WriteBootstrap file:\n"
850 "%s: ERR=%s\n"), fname, be.bstrerror());
851 jcr->setJobStatus(JS_ErrorTerminated);
853 free_pool_memory(fname);