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-2006 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 * The Device can change at the end of a tape, so refresh it
95 * We just have a new tape up, now read the label (first record)
96 * and pass it off to the callback routine, then continue
97 * most likely reading the previous record.
99 read_block_from_device(dcr, NO_BLOCK_NUMBER_CHECK);
100 read_record_from_block(block, trec);
101 handle_session_record(dev, trec, &sessrec);
102 ok = record_cb(dcr, trec);
104 position_to_first_file(jcr, dev);
105 /* After reading label, we must read first data block */
108 } else if (dev->at_eof()) {
110 Jmsg(jcr, M_INFO, 0, _("End of file %u on device %s, Volume \"%s\"\n"),
111 dev->file, dev->print_name(), dcr->VolumeName);
113 Dmsg3(200, "End of file %u on device %s, Volume \"%s\"\n",
114 dev->file, dev->print_name(), dcr->VolumeName);
116 } else if (dev->is_short_block()) {
117 Jmsg1(jcr, M_ERROR, 0, "%s", dev->errmsg);
120 /* I/O error or strange end of tape */
121 display_tape_error_status(jcr, dev);
122 if (forge_on || jcr->ignore_label_errors) {
123 dev->fsr(1); /* try skipping bad record */
124 Pmsg0(000, _("Did fsr\n"));
125 continue; /* try to continue */
127 ok = false; /* stop everything */
131 Dmsg2(300, "New block at position=(file:block) %u:%u\n", dev->file, dev->block_num);
132 #ifdef if_and_when_FAST_BLOCK_REJECTION_is_working
133 /* this does not stop when file/block are too big */
134 if (!match_bsr_block(jcr->bsr, block)) {
135 if (try_repositioning(jcr, rec, dev)) {
136 break; /* get next volume */
138 continue; /* skip this record */
143 * Get a new record for each Job as defined by
144 * VolSessionId and VolSessionTime
147 foreach_dlist(rec, recs) {
148 if (rec->VolSessionId == block->VolSessionId &&
149 rec->VolSessionTime == block->VolSessionTime) {
157 Dmsg2(300, "New record for SI=%d ST=%d\n",
158 block->VolSessionId, block->VolSessionTime);
160 Dmsg3(300, "After mount next vol. stat=%s blk=%d rem=%d\n", rec_state_to_str(rec),
161 block->BlockNumber, rec->remainder);
164 Dmsg1(300, "Block empty %d\n", is_block_empty(rec));
165 for (rec->state=0; ok && !is_block_empty(rec); ) {
166 if (!read_record_from_block(block, rec)) {
167 Dmsg3(400, "!read-break. state=%s blk=%d rem=%d\n", rec_state_to_str(rec),
168 block->BlockNumber, rec->remainder);
171 Dmsg5(300, "read-OK. state=%s blk=%d rem=%d file:block=%u:%u\n",
172 rec_state_to_str(rec), block->BlockNumber, rec->remainder,
173 dev->file, dev->block_num);
175 * At this point, we have at least a record header.
176 * Now decide if we want this record or not, but remember
177 * before accessing the record, we may need to read again to
181 Dmsg6(300, "recno=%d state=%s blk=%d SI=%d ST=%d FI=%d\n", record,
182 rec_state_to_str(rec), block->BlockNumber,
183 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex);
185 if (rec->FileIndex == EOM_LABEL) { /* end of tape? */
186 Dmsg0(40, "Get EOM LABEL\n");
187 break; /* yes, get out */
190 /* Some sort of label? */
191 if (rec->FileIndex < 0) {
192 handle_session_record(dev, rec, &sessrec);
193 ok = record_cb(dcr, rec);
194 if (rec->FileIndex == EOS_LABEL) {
195 Dmsg2(300, "Remove rec. SI=%d ST=%d\n", rec->VolSessionId,
196 rec->VolSessionTime);
201 } /* end if label record */
207 int stat = match_bsr(jcr->bsr, rec, &dev->VolHdr, &sessrec);
208 if (stat == -1) { /* no more possible matches */
209 done = true; /* all items found, stop */
210 Dmsg2(300, "All done=(file:block) %u:%u\n", dev->file, dev->block_num);
212 } else if (stat == 0) { /* no match */
213 Dmsg4(300, "BSR no match: clear rem=%d FI=%d before set_eof pos %u:%u\n",
214 rec->remainder, rec->FileIndex, dev->file, dev->block_num);
216 rec->state &= ~REC_PARTIAL_RECORD;
217 if (try_repositioning(jcr, rec, dev)) {
220 continue; /* we don't want record, read next one */
223 dcr->VolLastIndex = rec->FileIndex; /* let caller know where we are */
224 if (is_partial_record(rec)) {
225 Dmsg6(300, "Partial, break. recno=%d state=%s blk=%d SI=%d ST=%d FI=%d\n", record,
226 rec_state_to_str(rec), block->BlockNumber,
227 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex);
228 break; /* read second part of record */
230 ok = record_cb(dcr, rec);
231 if (crypto_digest_stream_type(rec->Stream) != CRYPTO_DIGEST_NONE) {
232 Dmsg3(300, "Done FI=%u before set_eof pos %u:%u\n", rec->FileIndex,
233 dev->file, dev->block_num);
234 if (match_set_eof(jcr->bsr, rec) && try_repositioning(jcr, rec, dev)) {
235 Dmsg2(300, "Break after match_set_eof pos %u:%u\n",
236 dev->file, dev->block_num);
239 Dmsg2(300, "After set_eof pos %u:%u\n", dev->file, dev->block_num);
241 } /* end for loop over records */
242 Dmsg2(300, "After end records position=(file:block) %u:%u\n", dev->file, dev->block_num);
243 } /* end for loop over blocks */
244 // Dmsg2(300, "Position=(file:block) %u:%u\n", dev->file, dev->block_num);
246 /* Walk down list and free all remaining allocated recs */
247 while (!recs->empty()) {
248 rec = (DEV_RECORD *)recs->first();
253 print_block_read_errors(jcr, block);
258 * See if we can reposition.
259 * Returns: true if at end of volume
262 static bool try_repositioning(JCR *jcr, DEV_RECORD *rec, DEVICE *dev)
265 bsr = find_next_bsr(jcr->bsr, dev);
266 if (bsr == NULL && jcr->bsr->mount_next_volume) {
267 Dmsg0(300, "Would mount next volume here\n");
268 Dmsg2(300, "Current postion (file:block) %u:%u\n",
269 dev->file, dev->block_num);
270 jcr->bsr->mount_next_volume = false;
271 if (!dev->at_eot()) {
272 /* Set EOT flag to force mount of next Volume */
273 jcr->mount_next_volume = true;
281 Jmsg(jcr, M_INFO, 0, _("Reposition from (file:block) %u:%u to %u:%u\n"),
282 dev->file, dev->block_num, bsr->volfile->sfile,
283 bsr->volblock->sblock);
285 Dmsg4(300, "Try_Reposition from (file:block) %u:%u to %u:%u\n",
286 dev->file, dev->block_num, bsr->volfile->sfile,
287 bsr->volblock->sblock);
288 dev->reposition(bsr->volfile->sfile, bsr->volblock->sblock);
295 * Position to the first file on this volume
297 static BSR *position_to_first_file(JCR *jcr, DEVICE *dev)
301 * Now find and position to first file and block
305 jcr->bsr->reposition = true; /* force repositioning */
306 bsr = find_next_bsr(jcr->bsr, dev);
307 if (bsr && (bsr->volfile->sfile != 0 || bsr->volblock->sblock != 0)) {
308 Jmsg(jcr, M_INFO, 0, _("Forward spacing to file:block %u:%u.\n"),
309 bsr->volfile->sfile, bsr->volblock->sblock);
310 Dmsg2(300, "Forward spacing to file:block %u:%u.\n",
311 bsr->volfile->sfile, bsr->volblock->sblock);
312 dev->reposition(bsr->volfile->sfile, bsr->volblock->sblock);
319 static void handle_session_record(DEVICE *dev, DEV_RECORD *rec, SESSION_LABEL *sessrec)
324 memset(sessrec, 0, sizeof(sessrec));
325 switch (rec->FileIndex) {
327 rtype = _("Fresh Volume Label");
330 rtype = _("Volume Label");
331 unser_volume_label(dev, rec);
334 rtype = _("Begin Session");
335 unser_session_label(sessrec, rec);
338 rtype = _("End Session");
341 rtype = _("End of Media");
344 bsnprintf(buf, sizeof(buf), _("Unknown code %d\n"), rec->FileIndex);
348 Dmsg5(300, _("%s Record: VolSessionId=%d VolSessionTime=%d JobId=%d DataLen=%d\n"),
349 rtype, rec->VolSessionId, rec->VolSessionTime, rec->Stream, rec->data_len);
353 static char *rec_state_to_str(DEV_RECORD *rec)
355 static char buf[200];
357 if (rec->state & REC_NO_HEADER) {
358 bstrncat(buf, "Nohdr,", sizeof(buf));
360 if (is_partial_record(rec)) {
361 bstrncat(buf, "partial,", sizeof(buf));
363 if (rec->state & REC_BLOCK_EMPTY) {
364 bstrncat(buf, "empty,", sizeof(buf));
366 if (rec->state & REC_NO_MATCH) {
367 bstrncat(buf, "Nomatch,", sizeof(buf));
369 if (rec->state & REC_CONTINUATION) {
370 bstrncat(buf, "cont,", sizeof(buf));
373 buf[strlen(buf)-1] = 0;