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)
244 /* In base level, no previous job is used and no restart incomplete jobs */
245 if (jcr->is_canceled() || jcr->is_JobLevel(L_BASE)) {
248 if (!jcr->accurate) {
252 if (jcr->is_JobLevel(L_FULL)) {
253 /* On Full mode, if no previous base job, no accurate things */
254 if (get_base_jobids(jcr, &jobids)) {
256 Jmsg(jcr, M_INFO, 0, _("Using BaseJobId(s): %s\n"), jobids.list);
261 /* For Incr/Diff level, we search for older jobs */
262 db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, &jobids);
264 /* We are in Incr/Diff, but no Full to build the accurate list... */
265 if (jobids.count == 0) {
266 Jmsg(jcr, M_FATAL, 0, _("Cannot find previous jobids.\n"));
267 return false; /* fail */
271 /* Don't send and store the checksum if fileset doesn't require it */
272 jcr->use_accurate_chksum = is_checksum_needed_by_fileset(jcr);
274 if (jcr->JobId) { /* display the message only for real jobs */
275 Jmsg(jcr, M_INFO, 0, _("Sending Accurate information.\n"));
278 /* to be able to allocate the right size for htable */
279 Mmsg(buf, "SELECT sum(JobFiles) FROM Job WHERE JobId IN (%s)", jobids.list);
280 db_sql_query(jcr->db, buf.c_str(), db_list_handler, &nb);
281 Dmsg2(200, "jobids=%s nb=%s\n", jobids.list, nb.list);
282 jcr->file_bsock->fsend("accurate files=%s\n", nb.list);
284 if (!db_open_batch_connexion(jcr, jcr->db)) {
285 Jmsg0(jcr, M_FATAL, 0, "Can't get batch sql connexion");
286 return false; /* Fail */
290 jcr->nb_base_files = str_to_int64(nb.list);
291 db_create_base_file_list(jcr, jcr->db, jobids.list);
292 db_get_base_file_list(jcr, jcr->db, jcr->use_accurate_chksum,
293 accurate_list_handler, (void *)jcr);
296 db_get_file_list(jcr, jcr->db_batch,
297 jobids.list, jcr->use_accurate_chksum, false /* no delta */,
298 accurate_list_handler, (void *)jcr);
301 /* TODO: close the batch connection ? (can be used very soon) */
303 jcr->file_bsock->signal(BNET_EOD);
308 * Do a backup of the specified FileSet
310 * Returns: false on failure
313 bool do_backup(JCR *jcr)
316 int tls_need = BNET_TLS_NONE;
323 if (jcr->is_JobLevel(L_VIRTUAL_FULL)) {
324 return do_vbackup(jcr);
327 /* Print Job Start message */
328 Jmsg(jcr, M_INFO, 0, _("Start Backup JobId %s, Job=%s\n"),
329 edit_uint64(jcr->JobId, ed1), jcr->Job);
331 jcr->setJobStatus(JS_Running);
332 Dmsg2(100, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
333 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
334 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
339 * Open a message channel connection with the Storage
340 * daemon. This is to let him know that our client
341 * will be contacting him for a backup session.
344 Dmsg0(110, "Open connection with storage daemon\n");
345 jcr->setJobStatus(JS_WaitSD);
347 * Start conversation with Storage daemon
349 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
353 * Now start a job with the Storage daemon
355 if (!start_storage_daemon_job(jcr, NULL, jcr->wstorage)) {
360 * Start the job prior to starting the message thread below
361 * to avoid two threads from using the BSOCK structure at
364 if (!jcr->store_bsock->fsend("run")) {
369 * Now start a Storage daemon message thread. Note,
370 * this thread is used to provide the catalog services
371 * for the backup job, including inserting the attributes
372 * into the catalog. See catalog_update() in catreq.c
374 if (!start_storage_daemon_message_thread(jcr)) {
377 Dmsg0(150, "Storage daemon connection OK\n");
379 jcr->setJobStatus(JS_WaitFD);
380 if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
384 jcr->setJobStatus(JS_Running);
385 fd = jcr->file_bsock;
387 if (!send_level_command(jcr)) {
391 if (!send_include_list(jcr)) {
395 if (!send_exclude_list(jcr)) {
400 * send Storage daemon address to the File daemon
403 if (store->SDDport == 0) {
404 store->SDDport = store->SDport;
407 /* TLS Requirement */
408 if (store->tls_enable) {
409 if (store->tls_require) {
410 tls_need = BNET_TLS_REQUIRED;
412 tls_need = BNET_TLS_OK;
416 fd->fsend(storaddr, store->address, store->SDDport, tls_need);
417 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
421 if (!send_runscripts_commands(jcr)) {
426 * We re-update the job start record so that the start
427 * time is set after the run before job. This avoids
428 * that any files created by the run before job will
429 * be saved twice. They will be backed up in the current
430 * job, but not in the next one unless they are changed.
431 * Without this, they will be backed up in this job and
432 * in the next job run because in that case, their date
433 * is after the start of this run.
435 jcr->start_time = time(NULL);
436 jcr->jr.StartTime = jcr->start_time;
437 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
438 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
442 * If backup is in accurate mode, we send the list of
445 if (!send_accurate_current_files(jcr)) {
446 goto bail_out; /* error */
449 /* Send backup command */
450 fd->fsend(backupcmd, jcr->JobFiles);
451 Dmsg1(100, ">filed: %s", fd->msg);
452 if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) {
456 /* Pickup Job termination data */
457 stat = wait_for_job_termination(jcr);
458 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
460 if (jcr->HasBase && !db_commit_base_file_attributes_record(jcr, jcr->db)) {
461 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
464 if (stat == JS_Terminated) {
465 backup_cleanup(jcr, stat);
470 /* Come here only after starting SD thread */
472 jcr->setJobStatus(JS_ErrorTerminated);
473 Dmsg1(400, "wait for sd. use=%d\n", jcr->use_count());
475 wait_for_job_termination(jcr, FDConnectTimeout);
476 Dmsg1(400, "after wait for sd. use=%d\n", jcr->use_count());
482 * Here we wait for the File daemon to signal termination,
483 * then we wait for the Storage daemon. When both
484 * are done, we return the job status.
485 * Also used by restore.c
487 int wait_for_job_termination(JCR *jcr, int timeout)
490 BSOCK *fd = jcr->file_bsock;
492 uint32_t JobFiles, JobErrors;
493 uint32_t JobWarnings = 0;
494 uint64_t ReadBytes = 0;
495 uint64_t JobBytes = 0;
500 jcr->setJobStatus(JS_Running);
504 tid = start_bsock_timer(fd, timeout); /* TODO: New timeout directive??? */
506 /* Wait for Client to terminate */
507 while ((n = bget_dirmsg(fd)) >= 0) {
509 (sscanf(fd->msg, EndJob, &jcr->FDJobStatus, &JobFiles,
510 &ReadBytes, &JobBytes, &JobErrors, &VSS, &Encrypt) == 7 ||
511 sscanf(fd->msg, OldEndJob, &jcr->FDJobStatus, &JobFiles,
512 &ReadBytes, &JobBytes, &JobErrors) == 5)) {
514 jcr->setJobStatus(jcr->FDJobStatus);
515 Dmsg1(100, "FDStatus=%c\n", (char)jcr->JobStatus);
517 Jmsg(jcr, M_WARNING, 0, _("Unexpected Client Job message: %s\n"),
520 if (job_canceled(jcr)) {
525 stop_bsock_timer(tid);
528 if (is_bnet_error(fd)) {
530 Jmsg(jcr, M_FATAL, 0, _("Network error with FD during %s: ERR=%s\n"),
531 job_type_to_str(jcr->getJobType()), fd->bstrerror());
532 while (i++ < 10 && jcr->job->RescheduleIncompleteJobs && jcr->is_canceled()) {
537 fd->signal(BNET_TERMINATE); /* tell Client we are terminating */
541 * Force cancel in SD if failing, but not for Incomplete jobs
542 * so that we let the SD despool.
544 Dmsg5(100, "cancel=%d fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", jcr->is_canceled(), fd_ok, jcr->FDJobStatus,
545 jcr->JobStatus, jcr->SDJobStatus);
546 if (jcr->is_canceled() || (!jcr->job->RescheduleIncompleteJobs && !fd_ok)) {
547 Dmsg4(100, "fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", fd_ok, jcr->FDJobStatus,
548 jcr->JobStatus, jcr->SDJobStatus);
549 cancel_storage_daemon_job(jcr);
552 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
553 wait_for_storage_daemon_termination(jcr);
555 /* Return values from FD */
557 jcr->JobFiles = JobFiles;
558 jcr->JobErrors += JobErrors; /* Keep total errors */
559 jcr->ReadBytes = ReadBytes;
560 jcr->JobBytes = JobBytes;
561 jcr->JobWarnings = JobWarnings;
563 jcr->Encrypt = Encrypt;
565 Jmsg(jcr, M_FATAL, 0, _("No Job status returned from FD.\n"));
568 // Dmsg4(100, "fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", fd_ok, jcr->FDJobStatus,
569 // jcr->JobStatus, jcr->SDJobStatus);
571 /* Return the first error status we find Dir, FD, or SD */
572 if (!fd_ok || is_bnet_error(fd)) { /* if fd not set, that use !fd_ok */
573 jcr->FDJobStatus = JS_ErrorTerminated;
575 if (jcr->JobStatus != JS_Terminated) {
576 return jcr->JobStatus;
578 if (jcr->FDJobStatus != JS_Terminated) {
579 return jcr->FDJobStatus;
581 return jcr->SDJobStatus;
585 * Release resources allocated during backup.
587 void backup_cleanup(JCR *jcr, int TermCode)
589 char sdt[50], edt[50], schedt[50];
590 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], compress[50];
591 char ec6[30], ec7[30], ec8[30], elapsed[50];
592 char term_code[100], fd_term_msg[100], sd_term_msg[100];
593 const char *term_msg;
594 int msg_type = M_INFO;
597 double kbps, compression;
601 if (jcr->is_JobLevel(L_VIRTUAL_FULL)) {
602 vbackup_cleanup(jcr, TermCode);
606 Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
607 memset(&mr, 0, sizeof(mr));
608 memset(&cr, 0, sizeof(cr));
610 if (jcr->getJobStatus() == JS_Terminated &&
611 (jcr->JobErrors || jcr->SDErrors || jcr->JobWarnings)) {
612 TermCode = JS_Warnings;
615 update_job_end(jcr, TermCode);
617 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
618 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
619 db_strerror(jcr->db));
620 jcr->setJobStatus(JS_ErrorTerminated);
623 bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
624 if (!db_get_client_record(jcr, jcr->db, &cr)) {
625 Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
626 db_strerror(jcr->db));
629 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
630 if (!db_get_media_record(jcr, jcr->db, &mr)) {
631 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
632 mr.VolumeName, db_strerror(jcr->db));
633 jcr->setJobStatus(JS_ErrorTerminated);
636 update_bootstrap_file(jcr);
638 switch (jcr->JobStatus) {
640 if (jcr->JobErrors || jcr->SDErrors) {
641 term_msg = _("Backup OK -- with warnings");
643 term_msg = _("Backup OK");
647 term_msg = _("Backup failed -- incomplete");
650 term_msg = _("Backup OK -- with warnings");
653 case JS_ErrorTerminated:
654 term_msg = _("*** Backup Error ***");
655 msg_type = M_ERROR; /* Generate error message */
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 = _("Backup Canceled");
665 if (jcr->store_bsock) {
666 jcr->store_bsock->signal(BNET_TERMINATE);
667 if (jcr->SD_msg_chan) {
668 pthread_cancel(jcr->SD_msg_chan);
673 term_msg = term_code;
674 sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
677 bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
678 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
679 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
680 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
684 kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
686 if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
688 * Note, if the job has erred, most likely it did not write any
689 * tape, so suppress this "error" message since in that case
690 * it is normal. Or look at it the other way, only for a
691 * normal exit should we complain about this error.
693 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
694 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
696 jcr->VolumeName[0] = 0; /* none */
699 if (jcr->ReadBytes == 0) {
700 bstrncpy(compress, "None", sizeof(compress));
702 compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
703 if (compression < 0.5) {
704 bstrncpy(compress, "None", sizeof(compress));
706 bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
709 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
710 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
713 Mmsg(base_info, " Base files/Used files: %lld/%lld (%.2f%%)\n",
715 jcr->nb_base_files_used,
716 jcr->nb_base_files_used*100.0/jcr->nb_base_files);
718 // bmicrosleep(15, 0); /* for debugging SIGHUP */
720 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
721 " Build OS: %s %s %s\n"
724 " Backup Level: %s%s\n"
725 " Client: \"%s\" %s\n"
726 " FileSet: \"%s\" %s\n"
727 " Pool: \"%s\" (From %s)\n"
728 " Catalog: \"%s\" (From %s)\n"
729 " Storage: \"%s\" (From %s)\n"
730 " Scheduled time: %s\n"
733 " Elapsed time: %s\n"
735 " FD Files Written: %s\n"
736 " SD Files Written: %s\n"
737 " FD Bytes Written: %s (%sB)\n"
738 " SD Bytes Written: %s (%sB)\n"
740 " Software Compression: %s\n"
741 "%s" /* Basefile info */
745 " Volume name(s): %s\n"
746 " Volume Session Id: %d\n"
747 " Volume Session Time: %d\n"
748 " Last Volume Bytes: %s (%sB)\n"
749 " Non-fatal FD errors: %d\n"
751 " FD termination status: %s\n"
752 " SD termination status: %s\n"
753 " Termination: %s\n\n"),
754 BACULA, my_name, VERSION, LSMDATE,
755 HOST_OS, DISTNAME, DISTVER,
758 level_to_str(jcr->getJobLevel()), jcr->since,
759 jcr->client->name(), cr.Uname,
760 jcr->fileset->name(), jcr->FSCreateTime,
761 jcr->pool->name(), jcr->pool_source,
762 jcr->catalog->name(), jcr->catalog_source,
763 jcr->wstore->name(), jcr->wstore_source,
767 edit_utime(RunTime, elapsed, sizeof(elapsed)),
769 edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
770 edit_uint64_with_commas(jcr->SDJobFiles, ec2),
771 edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
772 edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
773 edit_uint64_with_commas(jcr->SDJobBytes, ec5),
774 edit_uint64_with_suffix(jcr->SDJobBytes, ec6),
778 jcr->VSS?_("yes"):_("no"),
779 jcr->Encrypt?_("yes"):_("no"),
780 jcr->accurate?_("yes"):_("no"),
784 edit_uint64_with_commas(mr.VolBytes, ec7),
785 edit_uint64_with_suffix(mr.VolBytes, ec8),
792 Dmsg0(100, "Leave backup_cleanup()\n");
795 void update_bootstrap_file(JCR *jcr)
797 /* Now update the bootstrap file if any */
798 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes &&
799 jcr->job->WriteBootstrap) {
803 POOLMEM *fname = get_pool_memory(PM_FNAME);
804 fname = edit_job_codes(jcr, fname, jcr->job->WriteBootstrap, "");
806 VOL_PARAMS *VolParams = NULL;
808 char edt[50], ed1[50], ed2[50];
812 bpipe = open_bpipe(fname+1, 0, "w"); /* skip first char "|" */
813 fd = bpipe ? bpipe->wfd : NULL;
815 /* ***FIXME*** handle BASE */
816 fd = fopen(fname, jcr->is_JobLevel(L_FULL)?"w+b":"a+b");
819 VolCount = db_get_job_volume_parameters(jcr, jcr->db, jcr->JobId,
822 Jmsg(jcr, M_ERROR, 0, _("Could not get Job Volume Parameters to "
823 "update Bootstrap file. ERR=%s\n"), db_strerror(jcr->db));
824 if (jcr->SDJobFiles != 0) {
825 jcr->setJobStatus(JS_ErrorTerminated);
829 /* Start output with when and who wrote it */
830 bstrftimes(edt, sizeof(edt), time(NULL));
831 fprintf(fd, "# %s - %s - %s%s\n", edt, jcr->jr.Job,
832 level_to_str(jcr->getJobLevel()), jcr->since);
833 for (int i=0; i < VolCount; i++) {
834 /* Write the record */
835 fprintf(fd, "Volume=\"%s\"\n", VolParams[i].VolumeName);
836 fprintf(fd, "MediaType=\"%s\"\n", VolParams[i].MediaType);
837 if (VolParams[i].Slot > 0) {
838 fprintf(fd, "Slot=%d\n", VolParams[i].Slot);
840 fprintf(fd, "VolSessionId=%u\n", jcr->VolSessionId);
841 fprintf(fd, "VolSessionTime=%u\n", jcr->VolSessionTime);
842 fprintf(fd, "VolAddr=%s-%s\n",
843 edit_uint64(VolParams[i].StartAddr, ed1),
844 edit_uint64(VolParams[i].EndAddr, ed2));
845 fprintf(fd, "FileIndex=%d-%d\n", VolParams[i].FirstIndex,
846 VolParams[i].LastIndex);
858 Jmsg(jcr, M_ERROR, 0, _("Could not open WriteBootstrap file:\n"
859 "%s: ERR=%s\n"), fname, be.bstrerror());
860 jcr->setJobStatus(JS_ErrorTerminated);
862 free_pool_memory(fname);