3 * This routine provides a routine that will handle all
4 * the gory little details of reading a record from a Bacula
5 * archive. It uses a callback to pass you each record in turn,
6 * as well as a callback for mounting the next tape. It takes
7 * care of reading blocks, applying the bsr, ...
8 * Note, this routine is really the heart of the restore routines,
9 * and we are *really* bit pushing here so be careful about making
12 * Kern E. Sibbald, August MMII
17 Copyright (C) 2000-2005 Kern Sibbald
19 This program is free software; you can redistribute it and/or
20 modify it under the terms of the GNU General Public License
21 version 2 as amended with additional clauses defined in the
22 file LICENSE in the main source directory.
24 This program is distributed in the hope that it will be useful,
25 but WITHOUT ANY WARRANTY; without even the implied warranty of
26 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 the file LICENSE for additional details.
34 /* Forward referenced functions */
35 static void handle_session_record(DEVICE *dev, DEV_RECORD *rec, SESSION_LABEL *sessrec);
36 static BSR *position_to_first_file(JCR *jcr, DEVICE *dev);
37 static bool try_repositioning(JCR *jcr, DEV_RECORD *rec, DEVICE *dev);
39 static char *rec_state_to_str(DEV_RECORD *rec);
42 bool read_records(DCR *dcr,
43 bool record_cb(DCR *dcr, DEV_RECORD *rec),
44 bool mount_cb(DCR *dcr))
47 DEVICE *dev = dcr->dev;
48 DEV_BLOCK *block = dcr->block;
49 DEV_RECORD *rec = NULL;
53 SESSION_LABEL sessrec;
54 dlist *recs; /* linked list of rec packets open */
56 recs = New(dlist(rec, &rec->link));
57 position_to_first_file(jcr, dev);
58 jcr->mount_next_volume = false;
60 for ( ; ok && !done; ) {
61 if (job_canceled(jcr)) {
65 if (!read_block_from_device(dcr, CHECK_BLOCK_NUMBERS)) {
67 DEV_RECORD *trec = new_record();
68 Jmsg(jcr, M_INFO, 0, _("End of Volume at file %u on device %s, Volume \"%s\"\n"),
69 dev->file, dev->print_name(), dcr->VolumeName);
71 Jmsg(jcr, M_INFO, 0, _("End of all volumes.\n"));
72 ok = false; /* Stop everything */
74 * Create EOT Label so that Media record may
75 * be properly updated because this is the last
78 trec->FileIndex = EOT_LABEL;
79 trec->File = dev->file;
80 ok = record_cb(dcr, trec);
82 if (jcr->mount_next_volume) {
83 jcr->mount_next_volume = false;
88 jcr->mount_next_volume = false;
90 * We just have a new tape up, now read the label (first record)
91 * and pass it off to the callback routine, then continue
92 * most likely reading the previous record.
94 read_block_from_device(dcr, NO_BLOCK_NUMBER_CHECK);
95 read_record_from_block(block, trec);
96 handle_session_record(dev, trec, &sessrec);
97 ok = record_cb(dcr, trec);
99 position_to_first_file(jcr, dev);
100 /* After reading label, we must read first data block */
103 } else if (dev->at_eof()) {
105 Jmsg(jcr, M_INFO, 0, _("Got EOF at file %u on device %s, Volume \"%s\"\n"),
106 dev->file, dev->print_name(), dcr->VolumeName);
108 Dmsg3(200, "Got EOF at file %u on device %s, Volume \"%s\"\n",
109 dev->file, dev->print_name(), dcr->VolumeName);
111 } else if (dev->is_short_block()) {
112 Jmsg1(jcr, M_ERROR, 0, "%s", dev->errmsg);
115 /* I/O error or strange end of tape */
116 display_tape_error_status(jcr, dev);
117 if (forge_on || jcr->ignore_label_errors) {
118 dev->fsr(1); /* try skipping bad record */
119 Pmsg0(000, _("Did fsr\n"));
120 continue; /* try to continue */
122 ok = false; /* stop everything */
126 Dmsg2(300, "New block at position=(file:block) %u:%u\n", dev->file, dev->block_num);
127 #ifdef if_and_when_FAST_BLOCK_REJECTION_is_working
128 /* this does not stop when file/block are too big */
129 if (!match_bsr_block(jcr->bsr, block)) {
130 if (try_repositioning(jcr, rec, dev)) {
131 break; /* get next volume */
133 continue; /* skip this record */
138 * Get a new record for each Job as defined by
139 * VolSessionId and VolSessionTime
142 foreach_dlist(rec, recs) {
143 if (rec->VolSessionId == block->VolSessionId &&
144 rec->VolSessionTime == block->VolSessionTime) {
152 Dmsg2(300, "New record for SI=%d ST=%d\n",
153 block->VolSessionId, block->VolSessionTime);
155 Dmsg3(300, "After mount next vol. stat=%s blk=%d rem=%d\n", rec_state_to_str(rec),
156 block->BlockNumber, rec->remainder);
159 Dmsg1(300, "Block empty %d\n", is_block_empty(rec));
160 for (rec->state=0; ok && !is_block_empty(rec); ) {
161 if (!read_record_from_block(block, rec)) {
162 Dmsg3(400, "!read-break. state=%s blk=%d rem=%d\n", rec_state_to_str(rec),
163 block->BlockNumber, rec->remainder);
166 Dmsg5(300, "read-OK. state=%s blk=%d rem=%d file:block=%u:%u\n",
167 rec_state_to_str(rec), block->BlockNumber, rec->remainder,
168 dev->file, dev->block_num);
170 * At this point, we have at least a record header.
171 * Now decide if we want this record or not, but remember
172 * before accessing the record, we may need to read again to
176 Dmsg6(300, "recno=%d state=%s blk=%d SI=%d ST=%d FI=%d\n", record,
177 rec_state_to_str(rec), block->BlockNumber,
178 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex);
180 if (rec->FileIndex == EOM_LABEL) { /* end of tape? */
181 Dmsg0(40, "Get EOM LABEL\n");
182 break; /* yes, get out */
185 /* Some sort of label? */
186 if (rec->FileIndex < 0) {
187 handle_session_record(dev, rec, &sessrec);
188 ok = record_cb(dcr, rec);
189 if (rec->FileIndex == EOS_LABEL) {
190 Dmsg2(300, "Remove rec. SI=%d ST=%d\n", rec->VolSessionId,
191 rec->VolSessionTime);
196 } /* end if label record */
202 int stat = match_bsr(jcr->bsr, rec, &dev->VolHdr, &sessrec);
203 if (stat == -1) { /* no more possible matches */
204 done = true; /* all items found, stop */
205 Dmsg2(300, "All done=(file:block) %u:%u\n", dev->file, dev->block_num);
207 } else if (stat == 0) { /* no match */
208 Dmsg4(300, "Clear rem=%d FI=%d before set_eof pos %u:%u\n",
209 rec->remainder, rec->FileIndex, dev->file, dev->block_num);
211 rec->state &= ~REC_PARTIAL_RECORD;
212 if (try_repositioning(jcr, rec, dev)) {
215 continue; /* we don't want record, read next one */
218 dcr->VolLastIndex = rec->FileIndex; /* let caller know where we are */
219 if (is_partial_record(rec)) {
220 Dmsg6(300, "Partial, break. recno=%d state=%s blk=%d SI=%d ST=%d FI=%d\n", record,
221 rec_state_to_str(rec), block->BlockNumber,
222 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex);
223 break; /* read second part of record */
225 ok = record_cb(dcr, rec);
226 if (crypto_digest_stream_type(rec->Stream) != CRYPTO_DIGEST_NONE) {
227 Dmsg3(300, "Done FI=%u before set_eof pos %u:%u\n", rec->FileIndex,
228 dev->file, dev->block_num);
229 if (match_set_eof(jcr->bsr, rec) && try_repositioning(jcr, rec, dev)) {
230 Dmsg2(300, "Break after match_set_eof pos %u:%u\n",
231 dev->file, dev->block_num);
234 Dmsg2(300, "After set_eof pos %u:%u\n", dev->file, dev->block_num);
236 } /* end for loop over records */
237 Dmsg2(300, "After end records position=(file:block) %u:%u\n", dev->file, dev->block_num);
238 } /* end for loop over blocks */
239 // Dmsg2(300, "Position=(file:block) %u:%u\n", dev->file, dev->block_num);
241 /* Walk down list and free all remaining allocated recs */
242 while (!recs->empty()) {
243 rec = (DEV_RECORD *)recs->first();
248 print_block_read_errors(jcr, block);
253 * See if we can reposition.
254 * Returns: true if at end of volume
257 static bool try_repositioning(JCR *jcr, DEV_RECORD *rec, DEVICE *dev)
260 bsr = find_next_bsr(jcr->bsr, dev);
261 if (bsr == NULL && jcr->bsr->mount_next_volume) {
262 Dmsg0(300, "Would mount next volume here\n");
263 Dmsg2(300, "Current postion (file:block) %u:%u\n",
264 dev->file, dev->block_num);
265 jcr->bsr->mount_next_volume = false;
266 if (!dev->at_eot()) {
267 /* Set EOT flag to force mount of next Volume */
268 jcr->mount_next_volume = true;
276 Jmsg(jcr, M_INFO, 0, _("Reposition from (file:block) %u:%u to %u:%u\n"),
277 dev->file, dev->block_num, bsr->volfile->sfile,
278 bsr->volblock->sblock);
280 Dmsg4(300, "Try_Reposition from (file:block) %u:%u to %u:%u\n",
281 dev->file, dev->block_num, bsr->volfile->sfile,
282 bsr->volblock->sblock);
283 reposition_dev(dev, bsr->volfile->sfile, bsr->volblock->sblock);
290 * Position to the first file on this volume
292 static BSR *position_to_first_file(JCR *jcr, DEVICE *dev)
296 * Now find and position to first file and block
300 jcr->bsr->reposition = true; /* force repositioning */
301 bsr = find_next_bsr(jcr->bsr, dev);
302 if (bsr && (bsr->volfile->sfile != 0 || bsr->volblock->sblock != 0)) {
303 Jmsg(jcr, M_INFO, 0, _("Forward spacing to file:block %u:%u.\n"),
304 bsr->volfile->sfile, bsr->volblock->sblock);
305 Dmsg2(300, "Forward spacing to file:block %u:%u.\n",
306 bsr->volfile->sfile, bsr->volblock->sblock);
307 reposition_dev(dev, bsr->volfile->sfile, bsr->volblock->sblock);
314 static void handle_session_record(DEVICE *dev, DEV_RECORD *rec, SESSION_LABEL *sessrec)
319 memset(sessrec, 0, sizeof(sessrec));
320 switch (rec->FileIndex) {
322 rtype = _("Fresh Volume Label");
325 rtype = _("Volume Label");
326 unser_volume_label(dev, rec);
329 rtype = _("Begin Session");
330 unser_session_label(sessrec, rec);
333 rtype = _("End Session");
336 rtype = _("End of Media");
339 bsnprintf(buf, sizeof(buf), _("Unknown code %d\n"), rec->FileIndex);
343 Dmsg5(300, _("%s Record: VolSessionId=%d VolSessionTime=%d JobId=%d DataLen=%d\n"),
344 rtype, rec->VolSessionId, rec->VolSessionTime, rec->Stream, rec->data_len);
348 static char *rec_state_to_str(DEV_RECORD *rec)
350 static char buf[200];
352 if (rec->state & REC_NO_HEADER) {
353 bstrncat(buf, "Nohdr,", sizeof(buf));
355 if (is_partial_record(rec)) {
356 bstrncat(buf, "partial,", sizeof(buf));
358 if (rec->state & REC_BLOCK_EMPTY) {
359 bstrncat(buf, "empty,", sizeof(buf));
361 if (rec->state & REC_NO_MATCH) {
362 bstrncat(buf, "Nomatch,", sizeof(buf));
364 if (rec->state & REC_CONTINUATION) {
365 bstrncat(buf, "cont,", sizeof(buf));
368 buf[strlen(buf)-1] = 0;