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, false /* no delta */,
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)) {
402 /* TODO: See priority with bandwidth parameter */
403 if (jcr->job->max_bandwidth > 0) {
404 jcr->max_bandwidth = jcr->job->max_bandwidth;
405 } else if (jcr->client->max_bandwidth > 0) {
406 jcr->max_bandwidth = jcr->client->max_bandwidth;
409 if (jcr->max_bandwidth > 0) {
410 send_bwlimit(jcr, jcr->Job); /* Old clients don't have this command */
414 * send Storage daemon address to the File daemon
417 if (store->SDDport == 0) {
418 store->SDDport = store->SDport;
421 /* TLS Requirement */
422 if (store->tls_enable) {
423 if (store->tls_require) {
424 tls_need = BNET_TLS_REQUIRED;
426 tls_need = BNET_TLS_OK;
430 fd->fsend(storaddr, store->address, store->SDDport, tls_need);
431 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
435 if (!send_runscripts_commands(jcr)) {
440 * We re-update the job start record so that the start
441 * time is set after the run before job. This avoids
442 * that any files created by the run before job will
443 * be saved twice. They will be backed up in the current
444 * job, but not in the next one unless they are changed.
445 * Without this, they will be backed up in this job and
446 * in the next job run because in that case, their date
447 * is after the start of this run.
449 jcr->start_time = time(NULL);
450 jcr->jr.StartTime = jcr->start_time;
451 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
452 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
456 * If backup is in accurate mode, we send the list of
459 if (!send_accurate_current_files(jcr)) {
463 /* Send backup command */
464 fd->fsend(backupcmd);
465 if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) {
469 /* Pickup Job termination data */
470 stat = wait_for_job_termination(jcr);
471 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
474 !db_commit_base_file_attributes_record(jcr, jcr->db))
476 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
479 if (stat == JS_Terminated) {
480 backup_cleanup(jcr, stat);
485 /* Come here only after starting SD thread */
487 jcr->setJobStatus(JS_ErrorTerminated);
488 Dmsg1(400, "wait for sd. use=%d\n", jcr->use_count());
490 wait_for_job_termination(jcr, FDConnectTimeout);
491 Dmsg1(400, "after wait for sd. use=%d\n", jcr->use_count());
497 * Here we wait for the File daemon to signal termination,
498 * then we wait for the Storage daemon. When both
499 * are done, we return the job status.
500 * Also used by restore.c
502 int wait_for_job_termination(JCR *jcr, int timeout)
505 BSOCK *fd = jcr->file_bsock;
507 uint32_t JobFiles, JobErrors;
508 uint32_t JobWarnings = 0;
509 uint64_t ReadBytes = 0;
510 uint64_t JobBytes = 0;
515 jcr->setJobStatus(JS_Running);
519 tid = start_bsock_timer(fd, timeout); /* TODO: New timeout directive??? */
521 /* Wait for Client to terminate */
522 while ((n = bget_dirmsg(fd)) >= 0) {
524 (sscanf(fd->msg, EndJob, &jcr->FDJobStatus, &JobFiles,
525 &ReadBytes, &JobBytes, &JobErrors, &VSS, &Encrypt) == 7 ||
526 sscanf(fd->msg, OldEndJob, &jcr->FDJobStatus, &JobFiles,
527 &ReadBytes, &JobBytes, &JobErrors) == 5)) {
529 jcr->setJobStatus(jcr->FDJobStatus);
530 Dmsg1(100, "FDStatus=%c\n", (char)jcr->JobStatus);
532 Jmsg(jcr, M_WARNING, 0, _("Unexpected Client Job message: %s\n"),
535 if (job_canceled(jcr)) {
540 stop_bsock_timer(tid);
543 if (is_bnet_error(fd)) {
544 Jmsg(jcr, M_FATAL, 0, _("Network error with FD during %s: ERR=%s\n"),
545 job_type_to_str(jcr->getJobType()), fd->bstrerror());
547 fd->signal(BNET_TERMINATE); /* tell Client we are terminating */
550 /* Force cancel in SD if failing */
551 if (job_canceled(jcr) || !fd_ok) {
552 cancel_storage_daemon_job(jcr);
555 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
556 wait_for_storage_daemon_termination(jcr);
558 /* Return values from FD */
560 jcr->JobFiles = JobFiles;
561 jcr->JobErrors += JobErrors; /* Keep total errors */
562 jcr->ReadBytes = ReadBytes;
563 jcr->JobBytes = JobBytes;
564 jcr->JobWarnings = JobWarnings;
566 jcr->Encrypt = Encrypt;
568 Jmsg(jcr, M_FATAL, 0, _("No Job status returned from FD.\n"));
571 // Dmsg4(100, "fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", fd_ok, jcr->FDJobStatus,
572 // jcr->JobStatus, jcr->SDJobStatus);
574 /* Return the first error status we find Dir, FD, or SD */
575 if (!fd_ok || is_bnet_error(fd)) { /* if fd not set, that use !fd_ok */
576 jcr->FDJobStatus = JS_ErrorTerminated;
578 if (jcr->JobStatus != JS_Terminated) {
579 return jcr->JobStatus;
581 if (jcr->FDJobStatus != JS_Terminated) {
582 return jcr->FDJobStatus;
584 return jcr->SDJobStatus;
588 * Release resources allocated during backup.
590 void backup_cleanup(JCR *jcr, int TermCode)
592 char sdt[50], edt[50], schedt[50];
593 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], compress[50];
594 char ec6[30], ec7[30], ec8[30], elapsed[50];
595 char term_code[100], fd_term_msg[100], sd_term_msg[100];
596 const char *term_msg;
597 int msg_type = M_INFO;
600 double kbps, compression;
604 if (jcr->getJobLevel() == L_VIRTUAL_FULL) {
605 vbackup_cleanup(jcr, TermCode);
609 Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
610 memset(&mr, 0, sizeof(mr));
611 memset(&cr, 0, sizeof(cr));
614 if (jcr->getJobStatus() == JS_Terminated &&
615 (jcr->JobErrors || jcr->SDErrors || jcr->JobWarnings)) {
616 TermCode = JS_Warnings;
620 update_job_end(jcr, TermCode);
622 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
623 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
624 db_strerror(jcr->db));
625 jcr->setJobStatus(JS_ErrorTerminated);
628 bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
629 if (!db_get_client_record(jcr, jcr->db, &cr)) {
630 Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
631 db_strerror(jcr->db));
634 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
635 if (!db_get_media_record(jcr, jcr->db, &mr)) {
636 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
637 mr.VolumeName, db_strerror(jcr->db));
638 jcr->setJobStatus(JS_ErrorTerminated);
641 update_bootstrap_file(jcr);
643 switch (jcr->JobStatus) {
645 if (jcr->JobErrors || jcr->SDErrors) {
646 term_msg = _("Backup OK -- with warnings");
648 term_msg = _("Backup OK");
652 term_msg = _("Backup OK -- with warnings");
655 case JS_ErrorTerminated:
656 term_msg = _("*** Backup Error ***");
657 msg_type = M_ERROR; /* Generate error message */
658 if (jcr->store_bsock) {
659 jcr->store_bsock->signal(BNET_TERMINATE);
660 if (jcr->SD_msg_chan) {
661 pthread_cancel(jcr->SD_msg_chan);
666 term_msg = _("Backup Canceled");
667 if (jcr->store_bsock) {
668 jcr->store_bsock->signal(BNET_TERMINATE);
669 if (jcr->SD_msg_chan) {
670 pthread_cancel(jcr->SD_msg_chan);
675 term_msg = term_code;
676 sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
679 bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
680 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
681 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
682 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
686 kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
688 if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
690 * Note, if the job has erred, most likely it did not write any
691 * tape, so suppress this "error" message since in that case
692 * it is normal. Or look at it the other way, only for a
693 * normal exit should we complain about this error.
695 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
696 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
698 jcr->VolumeName[0] = 0; /* none */
701 if (jcr->ReadBytes == 0) {
702 bstrncpy(compress, "None", sizeof(compress));
704 compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
705 if (compression < 0.5) {
706 bstrncpy(compress, "None", sizeof(compress));
708 bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
711 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
712 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
715 Mmsg(base_info, " Base files/Used files: %lld/%lld (%.2f%%)\n",
717 jcr->nb_base_files_used,
718 jcr->nb_base_files_used*100.0/jcr->nb_base_files);
720 // bmicrosleep(15, 0); /* for debugging SIGHUP */
722 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
723 " Build OS: %s %s %s\n"
726 " Backup Level: %s%s\n"
727 " Client: \"%s\" %s\n"
728 " FileSet: \"%s\" %s\n"
729 " Pool: \"%s\" (From %s)\n"
730 " Catalog: \"%s\" (From %s)\n"
731 " Storage: \"%s\" (From %s)\n"
732 " Scheduled time: %s\n"
735 " Elapsed time: %s\n"
737 " FD Files Written: %s\n"
738 " SD Files Written: %s\n"
739 " FD Bytes Written: %s (%sB)\n"
740 " SD Bytes Written: %s (%sB)\n"
742 " Software Compression: %s\n"
743 "%s" /* Basefile info */
747 " Volume name(s): %s\n"
748 " Volume Session Id: %d\n"
749 " Volume Session Time: %d\n"
750 " Last Volume Bytes: %s (%sB)\n"
751 " Non-fatal FD errors: %d\n"
753 " FD termination status: %s\n"
754 " SD termination status: %s\n"
755 " Termination: %s\n\n"),
756 BACULA, my_name, VERSION, LSMDATE,
757 HOST_OS, DISTNAME, DISTVER,
760 level_to_str(jcr->getJobLevel()), jcr->since,
761 jcr->client->name(), cr.Uname,
762 jcr->fileset->name(), jcr->FSCreateTime,
763 jcr->pool->name(), jcr->pool_source,
764 jcr->catalog->name(), jcr->catalog_source,
765 jcr->wstore->name(), jcr->wstore_source,
769 edit_utime(RunTime, elapsed, sizeof(elapsed)),
771 edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
772 edit_uint64_with_commas(jcr->SDJobFiles, ec2),
773 edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
774 edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
775 edit_uint64_with_commas(jcr->SDJobBytes, ec5),
776 edit_uint64_with_suffix(jcr->SDJobBytes, ec6),
780 jcr->VSS?_("yes"):_("no"),
781 jcr->Encrypt?_("yes"):_("no"),
782 jcr->accurate?_("yes"):_("no"),
786 edit_uint64_with_commas(mr.VolBytes, ec7),
787 edit_uint64_with_suffix(mr.VolBytes, ec8),
794 Dmsg0(100, "Leave backup_cleanup()\n");
797 void update_bootstrap_file(JCR *jcr)
799 /* Now update the bootstrap file if any */
800 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes &&
801 jcr->job->WriteBootstrap) {
805 POOLMEM *fname = get_pool_memory(PM_FNAME);
806 fname = edit_job_codes(jcr, fname, jcr->job->WriteBootstrap, "");
808 VOL_PARAMS *VolParams = NULL;
810 char edt[50], ed1[50], ed2[50];
814 bpipe = open_bpipe(fname+1, 0, "w"); /* skip first char "|" */
815 fd = bpipe ? bpipe->wfd : NULL;
817 /* ***FIXME*** handle BASE */
818 fd = fopen(fname, jcr->getJobLevel()==L_FULL?"w+b":"a+b");
821 VolCount = db_get_job_volume_parameters(jcr, jcr->db, jcr->JobId,
824 Jmsg(jcr, M_ERROR, 0, _("Could not get Job Volume Parameters to "
825 "update Bootstrap file. ERR=%s\n"), db_strerror(jcr->db));
826 if (jcr->SDJobFiles != 0) {
827 jcr->setJobStatus(JS_ErrorTerminated);
831 /* Start output with when and who wrote it */
832 bstrftimes(edt, sizeof(edt), time(NULL));
833 fprintf(fd, "# %s - %s - %s%s\n", edt, jcr->jr.Job,
834 level_to_str(jcr->getJobLevel()), jcr->since);
835 for (int i=0; i < VolCount; i++) {
836 /* Write the record */
837 fprintf(fd, "Volume=\"%s\"\n", VolParams[i].VolumeName);
838 fprintf(fd, "MediaType=\"%s\"\n", VolParams[i].MediaType);
839 if (VolParams[i].Slot > 0) {
840 fprintf(fd, "Slot=%d\n", VolParams[i].Slot);
842 fprintf(fd, "VolSessionId=%u\n", jcr->VolSessionId);
843 fprintf(fd, "VolSessionTime=%u\n", jcr->VolSessionTime);
844 fprintf(fd, "VolAddr=%s-%s\n",
845 edit_uint64(VolParams[i].StartAddr, ed1),
846 edit_uint64(VolParams[i].EndAddr, ed2));
847 fprintf(fd, "FileIndex=%d-%d\n", VolParams[i].FirstIndex,
848 VolParams[i].LastIndex);
860 Jmsg(jcr, M_ERROR, 0, _("Could not open WriteBootstrap file:\n"
861 "%s: ERR=%s\n"), fname, be.bstrerror());
862 jcr->setJobStatus(JS_ErrorTerminated);
864 free_pool_memory(fname);