2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2016 Kern Sibbald
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
21 * This routine provides a routine that will handle all
22 * the gory little details of reading a record from a Bacula
23 * archive. It uses a callback to pass you each record in turn,
24 * as well as a callback for mounting the next tape. It takes
25 * care of reading blocks, applying the bsr, ...
26 * Note, this routine is really the heart of the restore routines,
27 * and we are *really* bit pushing here so be careful about making
30 * Kern E. Sibbald, August MMII
37 /* Forward referenced functions */
38 static void handle_session_record(DEVICE *dev, DEV_RECORD *rec, SESSION_LABEL *sessrec);
39 static BSR *position_to_first_file(JCR *jcr, DCR *dcr);
40 static bool try_repositioning(JCR *jcr, DEV_RECORD *rec, DCR *dcr);
42 static char *rec_state_bits_to_str(DEV_RECORD *rec);
45 static const int dbglvl = 190;
46 static const int no_FileIndex = -999999;
49 * This subroutine reads all the records and passes them back to your
50 * callback routine (also mount routine at EOM).
51 * You must not change any values in the DEV_RECORD packet
53 bool read_records(DCR *dcr,
54 bool record_cb(DCR *dcr, DEV_RECORD *rec),
55 bool mount_cb(DCR *dcr))
58 DEVICE *dev = dcr->dev;
59 DEV_BLOCK *block = dcr->block;
60 DEV_RECORD *rec = NULL;
62 int32_t lastFileIndex;
65 SESSION_LABEL sessrec;
66 dlist *recs; /* linked list of rec packets open */
68 recs = New(dlist(rec, &rec->link));
69 position_to_first_file(jcr, dcr);
70 jcr->mount_next_volume = false;
72 for ( ; ok && !done; ) {
73 if (job_canceled(jcr)) {
77 if (!dcr->read_block_from_device(CHECK_BLOCK_NUMBERS)) {
79 DEV_RECORD *trec = new_record();
80 Jmsg(jcr, M_INFO, 0, _("End of Volume at file %u on device %s, Volume \"%s\"\n"),
81 dev->file, dev->print_name(), dcr->VolumeName);
82 volume_unused(dcr); /* mark volume unused */
84 Jmsg(jcr, M_INFO, 0, _("End of all volumes.\n"));
85 ok = false; /* Stop everything */
87 * Create EOT Label so that Media record may
88 * be properly updated because this is the last
91 trec->FileIndex = EOT_LABEL;
92 trec->File = dev->file;
93 ok = record_cb(dcr, trec);
95 if (jcr->mount_next_volume) {
96 jcr->mount_next_volume = false;
101 jcr->mount_next_volume = false;
103 * The Device can change at the end of a tape, so refresh it
104 * and the block from the dcr.
109 * We just have a new tape up, now read the label (first record)
110 * and pass it off to the callback routine, then continue
111 * most likely reading the previous record.
113 dcr->read_block_from_device(NO_BLOCK_NUMBER_CHECK);
114 read_record_from_block(dcr, trec);
115 handle_session_record(dev, trec, &sessrec);
116 ok = record_cb(dcr, trec);
118 position_to_first_file(jcr, dcr);
119 /* After reading label, we must read first data block */
122 } else if (dev->at_eof()) {
123 Dmsg3(200, "End of file %u on device %s, Volume \"%s\"\n",
124 dev->file, dev->print_name(), dcr->VolumeName);
126 } else if (dev->is_short_block()) {
127 Jmsg1(jcr, M_ERROR, 0, "%s", dev->errmsg);
130 /* I/O error or strange end of tape */
131 display_tape_error_status(jcr, dev);
132 if (forge_on || jcr->ignore_label_errors) {
133 dev->fsr(1); /* try skipping bad record */
134 Pmsg0(000, _("Did fsr in attemp to skip bad record.\n"));
135 continue; /* try to continue */
137 ok = false; /* stop everything */
141 Dmsg2(dbglvl, "Read new block at pos=%u:%u\n", dev->file, dev->block_num);
142 #ifdef if_and_when_FAST_BLOCK_REJECTION_is_working
143 /* this does not stop when file/block are too big */
144 if (!match_bsr_block(jcr->bsr, block)) {
145 if (try_repositioning(jcr, rec, dcr)) {
146 break; /* get next volume */
148 continue; /* skip this record */
153 * Get a new record for each Job as defined by
154 * VolSessionId and VolSessionTime
157 foreach_dlist(rec, recs) {
158 if (rec->VolSessionId == block->VolSessionId &&
159 rec->VolSessionTime == block->VolSessionTime) {
167 Dmsg3(dbglvl, "New record for state=%s SI=%d ST=%d\n",
168 rec_state_bits_to_str(rec),
169 block->VolSessionId, block->VolSessionTime);
171 Dmsg3(dbglvl, "Before read rec loop. stat=%s blk=%d rem=%d\n", rec_state_bits_to_str(rec),
172 block->BlockNumber, rec->remainder);
175 lastFileIndex = no_FileIndex;
176 Dmsg1(dbglvl, "Block %s empty\n", is_block_marked_empty(rec)?"is":"NOT");
177 for (rec->state_bits=0; ok && !is_block_marked_empty(rec); ) {
178 if (!read_record_from_block(dcr, rec)) {
179 Dmsg3(200, "!read-break. state_bits=%s blk=%d rem=%d\n", rec_state_bits_to_str(rec),
180 block->BlockNumber, rec->remainder);
183 Dmsg5(dbglvl, "read-OK. state_bits=%s blk=%d rem=%d file:block=%u:%u\n",
184 rec_state_bits_to_str(rec), block->BlockNumber, rec->remainder,
185 dev->file, dev->block_num);
187 * At this point, we have at least a record header.
188 * Now decide if we want this record or not, but remember
189 * before accessing the record, we may need to read again to
193 Dmsg6(dbglvl, "recno=%d state_bits=%s blk=%d SI=%d ST=%d FI=%d\n", record,
194 rec_state_bits_to_str(rec), block->BlockNumber,
195 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex);
197 if (rec->FileIndex == EOM_LABEL) { /* end of tape? */
198 Dmsg0(40, "Get EOM LABEL\n");
199 break; /* yes, get out */
202 /* Some sort of label? */
203 if (rec->FileIndex < 0) {
204 handle_session_record(dev, rec, &sessrec);
206 /* We just check block FI and FT not FileIndex */
207 rec->match_stat = match_bsr_block(jcr->bsr, block);
212 * Note, we pass *all* labels to the callback routine. If
213 * he wants to know if they matched the bsr, then he must
214 * check the match_stat in the record */
215 ok = record_cb(dcr, rec);
218 * If this is the end of the Session (EOS) for this record
219 * we can remove the record. Note, there is a separate
220 * record to read each session. If a new session is seen
221 * a new record will be created at approx line 157 above.
222 * However, it seg faults in the for line at lineno 196.
224 if (rec->FileIndex == EOS_LABEL) {
225 Dmsg2(dbglvl, "Remove EOS rec. SI=%d ST=%d\n", rec->VolSessionId,
226 rec->VolSessionTime);
232 } /* end if label record */
238 rec->match_stat = match_bsr(jcr->bsr, rec, &dev->VolHdr, &sessrec, jcr);
239 Dmsg2(dbglvl, "match_bsr=%d bsr->reposition=%d\n", rec->match_stat,
240 jcr->bsr->reposition);
241 if (rec->match_stat == -1) { /* no more possible matches */
242 done = true; /* all items found, stop */
243 Dmsg2(dbglvl, "All done=(file:block) %u:%u\n", dev->file, dev->block_num);
245 } else if (rec->match_stat == 0) { /* no match */
246 Dmsg4(dbglvl, "BSR no match: clear rem=%d FI=%d before set_eof pos %u:%u\n",
247 rec->remainder, rec->FileIndex, dev->file, dev->block_num);
249 rec->state_bits &= ~REC_PARTIAL_RECORD;
250 if (try_repositioning(jcr, rec, dcr)) {
253 continue; /* we don't want record, read next one */
256 dcr->VolLastIndex = rec->FileIndex; /* let caller know where we are */
257 if (is_partial_record(rec)) {
258 Dmsg6(dbglvl, "Partial, break. recno=%d state_bits=%s blk=%d SI=%d ST=%d FI=%d\n", record,
259 rec_state_bits_to_str(rec), block->BlockNumber,
260 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex);
261 break; /* read second part of record */
264 Dmsg6(dbglvl, "OK callback. recno=%d state_bits=%s blk=%d SI=%d ST=%d FI=%d\n", record,
265 rec_state_bits_to_str(rec), block->BlockNumber,
266 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex);
267 if (lastFileIndex != no_FileIndex && lastFileIndex != rec->FileIndex) {
268 if (is_this_bsr_done(jcr->bsr, rec) && try_repositioning(jcr, rec, dcr)) {
269 Dmsg2(dbglvl, "This bsr done, break pos %u:%u\n",
270 dev->file, dev->block_num);
273 Dmsg2(dbglvl, "==== inside LastIndex=%d FileIndex=%d\n", lastFileIndex, rec->FileIndex);
275 Dmsg2(dbglvl, "==== LastIndex=%d FileIndex=%d\n", lastFileIndex, rec->FileIndex);
276 lastFileIndex = rec->FileIndex;
277 ok = record_cb(dcr, rec);
280 * If we have a digest stream, we check to see if we have
281 * finished the current bsr, and if so, repositioning will
284 if (crypto_digest_stream_type(rec->Stream) != CRYPTO_DIGEST_NONE) {
285 Dmsg3(dbglvl, "=== Have digest FI=%u before bsr check pos %u:%u\n", rec->FileIndex,
286 dev->file, dev->block_num);
287 if (is_this_bsr_done(jcr->bsr, rec) && try_repositioning(jcr, rec, dcr)) {
288 Dmsg1(dbglvl, "==== BSR done at FI=%d\n", rec->FileIndex);
289 Dmsg2(dbglvl, "This bsr done, break pos %u:%u\n",
290 dev->file, dev->block_num);
293 Dmsg2(900, "After is_bsr_done pos %u:%u\n", dev->file, dev->block_num);
296 } /* end for loop over records */
297 Dmsg2(dbglvl, "After end recs in block. pos=%u:%u\n", dev->file, dev->block_num);
298 } /* end for loop over blocks */
299 // Dmsg2(dbglvl, "Position=(file:block) %u:%u\n", dev->file, dev->block_num);
301 /* Walk down list and free all remaining allocated recs */
302 while (!recs->empty()) {
303 rec = (DEV_RECORD *)recs->first();
308 print_block_read_errors(jcr, block);
313 * See if we can reposition.
314 * Returns: true if at end of volume
317 static bool try_repositioning(JCR *jcr, DEV_RECORD *rec, DCR *dcr)
320 DEVICE *dev = dcr->dev;
322 bsr = find_next_bsr(jcr->bsr, dev);
323 Dmsg2(dbglvl, "nextbsr=%p mount_next_volume=%d\n", bsr, jcr->bsr->mount_next_volume);
324 if (bsr == NULL && jcr->bsr->mount_next_volume) {
325 Dmsg0(dbglvl, "Would mount next volume here\n");
326 Dmsg2(dbglvl, "Current postion (file:block) %u:%u\n",
327 dev->file, dev->block_num);
328 jcr->bsr->mount_next_volume = false;
329 if (!dev->at_eot()) {
330 /* Set EOT flag to force mount of next Volume */
331 jcr->mount_next_volume = true;
339 * ***FIXME*** gross kludge to make disk seeking work. Remove
340 * when find_next_bsr() is fixed not to return a bsr already
343 uint32_t block, file;
344 /* TODO: use dev->file_addr ? */
345 uint64_t dev_addr = (((uint64_t) dev->file)<<32) | dev->block_num;
346 uint64_t bsr_addr = get_bsr_start_addr(bsr, &file, &block);
348 if (dev_addr > bsr_addr) {
351 Dmsg4(dbglvl, "Try_Reposition from (file:block) %u:%u to %u:%u\n",
352 dev->file, dev->block_num, file, block);
353 dev->reposition(dcr, file, block);
361 * Position to the first file on this volume
363 static BSR *position_to_first_file(JCR *jcr, DCR *dcr)
366 DEVICE *dev = dcr->dev;
367 uint32_t file, block;
369 * Now find and position to first file and block
373 jcr->bsr->reposition = true; /* force repositioning */
374 bsr = find_next_bsr(jcr->bsr, dev);
376 if (get_bsr_start_addr(bsr, &file, &block) > 0) {
377 Jmsg(jcr, M_INFO, 0, _("Forward spacing Volume \"%s\" to file:block %u:%u.\n"),
378 dev->VolHdr.VolumeName, file, block);
379 dev->reposition(dcr, file, block);
386 static void handle_session_record(DEVICE *dev, DEV_RECORD *rec, SESSION_LABEL *sessrec)
391 memset(sessrec, 0, sizeof(SESSION_LABEL));
392 switch (rec->FileIndex) {
394 rtype = _("Fresh Volume Label");
397 rtype = _("Volume Label");
398 unser_volume_label(dev, rec);
401 rtype = _("Begin Session");
402 unser_session_label(sessrec, rec);
405 rtype = _("End Session");
408 rtype = _("End of Media");
411 bsnprintf(buf, sizeof(buf), _("Unknown code %d\n"), rec->FileIndex);
415 Dmsg5(dbglvl, _("%s Record: VolSessionId=%d VolSessionTime=%d JobId=%d DataLen=%d\n"),
416 rtype, rec->VolSessionId, rec->VolSessionTime, rec->Stream, rec->data_len);
420 static char *rec_state_bits_to_str(DEV_RECORD *rec)
422 static char buf[200];
424 if (rec->state_bits & REC_NO_HEADER) {
425 bstrncat(buf, "Nohdr,", sizeof(buf));
427 if (is_partial_record(rec)) {
428 bstrncat(buf, "partial,", sizeof(buf));
430 if (rec->state_bits & REC_BLOCK_EMPTY) {
431 bstrncat(buf, "empty,", sizeof(buf));
433 if (rec->state_bits & REC_NO_MATCH) {
434 bstrncat(buf, "Nomatch,", sizeof(buf));
436 if (rec->state_bits & REC_CONTINUATION) {
437 bstrncat(buf, "cont,", sizeof(buf));
440 buf[strlen(buf)-1] = 0;