From: Alain Spineux Date: Thu, 4 Aug 2022 13:06:57 +0000 (+0200) Subject: Fix #9341 split BSR when a volume cycle is detected X-Git-Tag: Beta-15.0.0~530 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a24334bfe8a5e2061e999b174a4d749172d374d7;p=thirdparty%2Fbacula.git Fix #9341 split BSR when a volume cycle is detected --- diff --git a/bacula/src/dird/bsr.c b/bacula/src/dird/bsr.c index ac3a8f1a6..22e9fd951 100644 --- a/bacula/src/dird/bsr.c +++ b/bacula/src/dird/bsr.c @@ -776,6 +776,8 @@ bool open_bootstrap_file(JCR *jcr, bootstrap_info &info) UAContext *ua; info.bs = NULL; info.ua = NULL; + info.split_list = NULL; + info.next_split_off = NULL; if (!jcr->RestoreBootstrap) { return false; @@ -821,4 +823,101 @@ void close_bootstrap_file(bootstrap_info &info) free_ua_context(info.ua); info.ua = NULL; } + if (info.split_list) { + delete info.split_list; + info.split_list = NULL; + info.next_split_off = NULL; // no need to free anything + } +} + + +struct bsr_vol_list { + hlink link; + uint64_t address; + char volume[1]; +}; + +/* Generate a list of split position in the BSR to break any cycle + * returns true if a cycle was detected + */ +bool split_bsr_loop(JCR *jcr, bootstrap_info &info) +{ + UAContext *ua = info.ua; + FILE *bs = info.bs; + bsr_vol_list *p = NULL; + htable volumes(p, &p->link, 100); // list of volume that have already been used + + POOL_MEM storage(PM_NAME); + POOL_MEM volume(PM_NAME), last_volume(PM_NAME); + + uint32_t VolSessionId, VolSessionTime; + uint32_t prevVolSessionId = 0, prevVolSessionTime = 0; + + boffset_t start_section_offset; // the offset of the beginning of the section + boffset_t start_job_off; // the offset of the first section of the job + + bool first = true; + bool after_eof = false; // used to handle the after EOF inside the loop + + if (info.split_list == NULL) { + info.split_list = New(alist(100, owned_by_alist)); + } + while (true) { + boffset_t cur_off = ftello(bs); // off of the line + after_eof = (bfgets(ua->cmd, bs) == NULL); + if (!after_eof) { + parse_ua_args(ua); + if (ua->argc != 1) { + continue; // @ERIC we do the same in check_for_new_storage() + } + } + if (after_eof || strcasecmp(ua->argk[0], "Storage") == 0) { + if (first) { + first = false; + } else { + // This was the last part or we have reached the end of the file + if (strcmp(last_volume.c_str(), volume.c_str()) != 0) { + /* look if the volume has already been used before */ + bsr_vol_list *item = (bsr_vol_list *)volumes.lookup(volume.c_str()); + if (item == NULL) { + /* this is the first time we use this volume */ + item = (bsr_vol_list *)volumes.hash_malloc(strlen(volume.c_str())+sizeof(bsr_vol_list)); + strcpy(item->volume, volume.c_str()); + item->address = 0; // unused + volumes.insert(item->volume, item); + + } else { + /* this volume has already been used before */ + boffset_t *p = (boffset_t *)malloc(sizeof(boffset_t)); + *p = start_job_off; + info.split_list->append(p); + } + } + last_volume.strcpy(volume.c_str()); + if (prevVolSessionTime != VolSessionTime || prevVolSessionId != VolSessionId) { + /* This is a new job */ + start_job_off = start_section_offset; + prevVolSessionId = VolSessionId; + prevVolSessionTime = VolSessionTime; + } + start_section_offset = cur_off; + } + if (after_eof) { + break; + } + storage.strcpy(ua->argv[0]); + } + if (strcasecmp(ua->argk[0], "Volume") == 0) { + volume.strcpy(ua->argv[0]); + } + if (strcasecmp(ua->argk[0], "VolSessionId") == 0) { + VolSessionId = str_to_uint64(ua->argv[0]); + } + if (strcasecmp(ua->argk[0], "VolSessionTime") == 0) { + VolSessionTime = str_to_uint64(ua->argv[0]); + } + } + fseeko(bs, 0, SEEK_SET); + info.next_split_off = (boffset_t *)info.split_list->first(); + return info.next_split_off != NULL; } diff --git a/bacula/src/dird/bsr.h b/bacula/src/dird/bsr.h index 732dadae7..da103e38b 100644 --- a/bacula/src/dird/bsr.h +++ b/bacula/src/dird/bsr.h @@ -67,6 +67,9 @@ struct bootstrap_info FILE *bs; UAContext *ua; char storage[MAX_NAME_LENGTH+1]; + alist *split_list; /* when a BSR cannot be restored in once, this is a list */ + /* of offset where to split the BSR to send to the SD */ + boffset_t *next_split_off; /* the next split position */ }; bool open_bootstrap_file(JCR *jcr, bootstrap_info &info); void close_bootstrap_file(bootstrap_info &info); diff --git a/bacula/src/dird/protos.h b/bacula/src/dird/protos.h index c19e05c5e..f92d90102 100644 --- a/bacula/src/dird/protos.h +++ b/bacula/src/dird/protos.h @@ -76,6 +76,7 @@ RBSR_FINDEX *new_findex(); void make_unique_restore_filename(UAContext *ua, POOLMEM **fname); void print_bsr(UAContext *ua, RESTORE_CTX &rx); void scan_bsr(JCR *jcr); +bool split_bsr_loop(JCR *jcr, bootstrap_info &info); /* catreq.c */ diff --git a/bacula/src/dird/restore.c b/bacula/src/dird/restore.c index c6c491140..6594cddba 100644 --- a/bacula/src/dird/restore.c +++ b/bacula/src/dird/restore.c @@ -192,6 +192,16 @@ static bool check_for_new_storage(JCR *jcr, bootstrap_info &info) return false; } +/* return true if the offset match the next offset where we need to split the BSR */ +static bool split_bsr(JCR *jcr, bootstrap_info &info, boffset_t off) +{ + if (info.next_split_off != NULL && *info.next_split_off == off) { + info.next_split_off = (boffset_t *)info.split_list->next(); // load next offset + return true; + } + return false; +} + /** * Send bootstrap file to Storage daemon section by section. */ @@ -209,8 +219,8 @@ static bool send_bootstrap_file(JCR *jcr, BSOCK *sock, } sock->fsend(bootstrap); pos = ftello(bs); - while(bfgets(ua->cmd, bs)) { - if (check_for_new_storage(jcr, info)) { + while (bfgets(ua->cmd, bs)) { + if (check_for_new_storage(jcr, info) || split_bsr(jcr, info, pos)) { /* Otherwise, we need to contact another storage daemon. * Reset bs to the beginning of the current segment. */ @@ -234,10 +244,12 @@ static bool select_rstore(JCR *jcr, bootstrap_info &info) STORE *store; int i; + /* Releases the store_bsock between calls to the SD. */ + free_bsock(jcr->store_bsock); STORE *rstore = jcr->store_mngr->get_rstore(); if (!strcmp(rstore->name(), info.storage)) { - return true; /* same SD nothing to change */ + return true; /* same SD nothing to change in the storage mngr*/ } if (!(store = (STORE *)GetResWithName(R_STORAGE,info.storage))) { @@ -247,12 +259,6 @@ static bool select_rstore(JCR *jcr, bootstrap_info &info) return false; } - /* - * This releases the store_bsock between calls to the SD. - * I think. - */ - free_bsock(jcr->store_bsock); - /* * release current read storage and get a new one */ @@ -260,7 +266,7 @@ static bool select_rstore(JCR *jcr, bootstrap_info &info) jcr->store_mngr->set_rstore(store, _("Job resource")); jcr->setJobStatus(JS_WaitSD); /* - * Wait for up to 6 hours to increment read stoage counter + * Wait for up to 6 hours to increment read storage counter */ for (i=0; i < MAX_TRIES; i++) { /* try to get read storage counter incremented */ @@ -302,11 +308,15 @@ bool restore_bootstrap(JCR *jcr) POOL_MEM restore_cmd(PM_MESSAGE); bool ret = false; - /* Open the bootstrap file */ if (!open_bootstrap_file(jcr, info)) { goto bail_out; } + + if (split_bsr_loop(jcr, info)) { /* create the split list to break volume cycle */ + Jmsg(jcr, M_INFO, 0, _("Found a volume cycle in the bootstrap, fixing automatically the reading process\n")); + } + /* Read the bootstrap file */ while (!feof(info.bs)) { @@ -316,8 +326,8 @@ bool restore_bootstrap(JCR *jcr) /** * Open a message channel connection with the Storage - * daemon. This is to let him know that our client - * will be contacting him for a backup session. + * daemon. This is to let him know that our client will be contacting + * him for a backup session. Or the opposite if sdcallsclient is enable * */ Dmsg0(10, "Open connection with storage daemon\n"); diff --git a/bacula/src/dird/vbackup.c b/bacula/src/dird/vbackup.c index 5bea21de4..c66adab45 100644 --- a/bacula/src/dird/vbackup.c +++ b/bacula/src/dird/vbackup.c @@ -120,6 +120,7 @@ bool do_vbackup(JCR *jcr) sellist sel; db_list_ctx jobids; UAContext *ua; + bootstrap_info info; Dmsg2(100, "rstorage=%p wstorage=%p\n", jcr->store_mngr->get_rstore_list(), jcr->store_mngr->get_wstore_list()); Dmsg2(100, "Read store=%s, write store=%s\n", @@ -308,6 +309,21 @@ _("This Job is not an Accurate backup so is not equivalent to a Full backup.\n") return false; } + /* Open the bootstrap file */ + if (!open_bootstrap_file(jcr, info)) { + return false; + } + + if (split_bsr_loop(jcr, info)) { /* create the split list to break volume cycle */ + Jmsg(jcr, M_FATAL, 0, _("Found a volume cycle in the bootstrap, Virtual Full is not possible on this Job\n")); + } + + close_bootstrap_file(info); + + if (jcr->is_canceled()) { + return false; + } + /* * Open a message channel connection with the Storage * daemon. This is to let him know that our client