1 /*
2    Bacula(R) - The Network Backup Solution
3 
4    Copyright (C) 2000-2020 Kern Sibbald
5 
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.
8 
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.
13 
14    This notice must be preserved when any source code is
15    conveyed and/or propagated.
16 
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20  *
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
28  *    any modifications.
29  *
30  *    Kern E. Sibbald, August MMII
31  *
32  */
33 
34 #include "bacula.h"
35 #include "stored.h"
36 
37 /* Forward referenced functions */
38 static BSR *position_to_first_file(JCR *jcr, DCR *dcr, BSR *bsr);
39 static void handle_session_record(DEVICE *dev, DEV_RECORD *rec, SESSION_LABEL *sessrec);
40 static bool try_repositioning(JCR *jcr, DEV_RECORD *rec, DCR *dcr);
41 #ifdef DEBUG
42 static char *rec_state_bits_to_str(DEV_RECORD *rec);
43 #endif
44 
45 static const int dbglvl = 150;
46 static const int no_FileIndex = -999999;
mount_next_vol(JCR * jcr,DCR * dcr,BSR * bsr,SESSION_LABEL * sessrec,bool * should_stop,bool record_cb (DCR * dcr,DEV_RECORD * rec),bool mount_cb (DCR * dcr))47 static bool mount_next_vol(JCR *jcr, DCR *dcr, BSR *bsr,
48                            SESSION_LABEL *sessrec, bool *should_stop,
49                            bool record_cb(DCR *dcr, DEV_RECORD *rec),
50                            bool mount_cb(DCR *dcr))
51 {
52    bool    ok = true;
53    DEVICE *dev = dcr->dev;
54    *should_stop = false;
55 
56    /* We need an other volume */
57    volume_unused(dcr);       /* mark volume unused */
58    if (!mount_cb(dcr)) {
59       *should_stop = true;
60       /*
61        * Create EOT Label so that Media record may
62        *  be properly updated because this is the last
63        *  tape.
64        */
65       DEV_RECORD *trec = new_record();
66       trec->FileIndex = EOT_LABEL;
67       trec->Addr = dev->get_full_addr();
68       ok = record_cb(dcr, trec);
69       free_record(trec);
70       if (jcr->mount_next_volume) {
71          jcr->mount_next_volume = false;
72          dev->clear_eot();
73       }
74       return ok;
75    }
76    jcr->mount_next_volume = false;
77    /*
78     * The Device can change at the end of a tape, so refresh it
79     *   from the dcr.
80     */
81    dev = dcr->dev;
82    /*
83     * We just have a new tape up, now read the label (first record)
84     *  and pass it off to the callback routine, then continue
85     *  most likely reading the previous record.
86     */
87    dcr->read_block_from_device(NO_BLOCK_NUMBER_CHECK);
88 
89    DEV_RECORD *trec = new_record();
90    read_record_from_block(dcr, trec);
91    handle_session_record(dev, trec, sessrec);
92    ok = record_cb(dcr, trec);
93    free_record(trec);
94    position_to_first_file(jcr, dcr, bsr); /* We jump to the specified position */
95    return ok;
96 }
97 
98 /*
99  * This subroutine reads all the records and passes them back to your
100  *  callback routine (also mount routine at EOM).
101  * You must not change any values in the DEV_RECORD packet
102  */
read_records(DCR * dcr,bool record_cb (DCR * dcr,DEV_RECORD * rec),bool mount_cb (DCR * dcr))103 bool read_records(DCR *dcr,
104        bool record_cb(DCR *dcr, DEV_RECORD *rec),
105        bool mount_cb(DCR *dcr))
106 {
107    JCR *jcr = dcr->jcr;
108    DEVICE *dev = dcr->dev;
109    DEV_BLOCK *block = dcr->block;
110    DEV_RECORD *rec = NULL;
111    uint32_t record;
112    int32_t lastFileIndex;
113    bool ok = true;
114    bool done = false;
115    bool should_stop;
116    SESSION_LABEL sessrec;
117    dlist *recs;                         /* linked list of rec packets open */
118    char ed1[50];
119    bool first_block = true;
120 
121    recs = New(dlist(rec, &rec->link));
122    /* We go to the first_file unless we need to reposition during an
123     * interactive restore session (the reposition will be done with a different
124     * BSR in the for loop */
125    position_to_first_file(jcr, dcr, jcr->bsr);
126    jcr->mount_next_volume = false;
127 
128    for ( ; ok && !done; ) {
129       if (job_canceled(jcr)) {
130          ok = false;
131          break;
132       }
133       ASSERT2(!dcr->dev->adata, "Called with adata block. Wrong!");
134 
135 
136       if (! first_block || dev->dev_type != B_FIFO_DEV ) {
137          if (dev->at_eot() || !dcr->read_block_from_device(CHECK_BLOCK_NUMBERS)) {
138             if (dev->at_eot()) {
139                Jmsg(jcr, M_INFO, 0,
140                     _("End of Volume \"%s\" at addr=%s on device %s.\n"),
141                     dcr->VolumeName,
142                     dev->print_addr(ed1, sizeof(ed1), dev->EndAddr),
143                     dev->print_name());
144                ok = mount_next_vol(jcr, dcr, jcr->bsr, &sessrec, &should_stop,
145                                    record_cb, mount_cb);
146                /* Might have changed after the mount request */
147                dev = dcr->dev;
148                block = dcr->block;
149                if (should_stop) {
150                   break;
151                }
152                continue;
153 
154             } else if (dev->at_eof()) {
155                Dmsg3(200, "EOF at addr=%s on device %s, Volume \"%s\"\n",
156                      dev->print_addr(ed1, sizeof(ed1), dev->EndAddr),
157                      dev->print_name(), dcr->VolumeName);
158                continue;
159             } else if (dev->is_short_block()) {
160                Jmsg1(jcr, M_ERROR, 0, "%s", dev->errmsg);
161                continue;
162             } else {
163                /* I/O error or strange end of tape */
164                display_tape_error_status(jcr, dev);
165                if (forge_on || jcr->ignore_label_errors) {
166                   dev->fsr(1);       /* try skipping bad record */
167                   Pmsg0(000, _("Did fsr in attempt to skip bad record.\n"));
168                   continue;              /* try to continue */
169                }
170                ok = false;               /* stop everything */
171                break;
172             }
173          }
174              Dmsg1(dbglvl, "Read new block at pos=%s\n", dev->print_addr(ed1, sizeof(ed1)));
175       }
176       first_block = false;
177 #ifdef if_and_when_FAST_BLOCK_REJECTION_is_working
178       /* this does not stop when file/block are too big */
179       if (!match_bsr_block(jcr->bsr, block)) {
180          if (try_repositioning(jcr, rec, dcr)) {
181             break;                    /* get next volume */
182          }
183          continue;                    /* skip this record */
184       }
185 #endif
186       /*
187        * Get a new record for each Job as defined by
188        *   VolSessionId and VolSessionTime
189        */
190       bool found = false;
191       foreach_dlist(rec, recs) {
192          if (rec->VolSessionId == block->VolSessionId &&
193              rec->VolSessionTime == block->VolSessionTime) {
194             /* When the previous Block of the current record is not correctly ordered,
195              * if we concat the previous record to the next one, the result is probably
196              * incorrect. At least the vacuum command should not use this kind of record
197              */
198             if (rec->remainder) {
199                if (rec->BlockNumber != (block->BlockNumber - 1)
200                    &&
201                    rec->BlockNumber != block->BlockNumber)
202                {
203                   Dmsg3(0, "invalid: rec=%ld block=%ld state=%s\n",
204                         rec->BlockNumber, block->BlockNumber, rec_state_bits_to_str(rec));
205                   rec->invalid = true;
206                   /* We can discard the current data if needed. The code is very
207                    * tricky in the read_records loop, so it's better to not
208                    * introduce new subtle errors.
209                    */
210                   if (dcr->discard_invalid_records) {
211                      empty_record(rec);
212                   }
213                }
214             }
215             found = true;
216             break;
217           }
218       }
219       if (!found) {
220          rec = new_record();
221          recs->prepend(rec);
222          Dmsg3(dbglvl, "New record for state=%s SI=%d ST=%d\n",
223              rec_state_bits_to_str(rec),
224              block->VolSessionId, block->VolSessionTime);
225       }
226       Dmsg4(dbglvl, "Before read rec loop. stat=%s blk=%d rem=%d invalid=%d\n",
227             rec_state_bits_to_str(rec), block->BlockNumber, rec->remainder, rec->invalid);
228       record = 0;
229       rec->state_bits = 0;
230       rec->BlockNumber = block->BlockNumber;
231       lastFileIndex = no_FileIndex;
232       Dmsg1(dbglvl, "Block %s empty\n", is_block_marked_empty(rec)?"is":"NOT");
233       for (rec->state_bits=0; ok && !is_block_marked_empty(rec); ) {
234          if (!read_record_from_block(dcr, rec)) {
235             Dmsg3(200, "!read-break. state_bits=%s blk=%d rem=%d\n", rec_state_bits_to_str(rec),
236                   block->BlockNumber, rec->remainder);
237             break;
238          }
239          Dmsg5(dbglvl, "read-OK. state_bits=%s blk=%d rem=%d volume:addr=%s:%llu\n",
240                rec_state_bits_to_str(rec), block->BlockNumber, rec->remainder,
241                NPRT(rec->VolumeName), rec->Addr);
242          /*
243           * At this point, we have at least a record header.
244           *  Now decide if we want this record or not, but remember
245           *  before accessing the record, we may need to read again to
246           *  get all the data.
247           */
248          record++;
249          Dmsg6(dbglvl, "recno=%d state_bits=%s blk=%d SI=%d ST=%d FI=%d\n", record,
250             rec_state_bits_to_str(rec), block->BlockNumber,
251             rec->VolSessionId, rec->VolSessionTime, rec->FileIndex);
252 
253          if (rec->FileIndex == EOM_LABEL) { /* end of tape? */
254             Dmsg0(40, "Get EOM LABEL\n");
255             break;                         /* yes, get out */
256          }
257 
258          /* Some sort of label? */
259          if (rec->FileIndex < 0) {
260             handle_session_record(dev, rec, &sessrec);
261             if (jcr->bsr) {
262                /* We just check block FI and FT not FileIndex */
263                rec->match_stat = match_bsr_block(jcr->bsr, block);
264             } else {
265                rec->match_stat = 0;
266             }
267             if (rec->invalid) {
268                Dmsg5(0, "The record %d in block %ld SI=%ld ST=%ld FI=%ld was marked as invalid\n",
269                      rec->RecNum, rec->BlockNumber, rec->VolSessionId, rec->VolSessionTime, rec->FileIndex);
270             }
271             //ASSERTD(!rec->invalid || dcr->discard_invalid_records, "Got an invalid record");
272             /*
273              * Note, we pass *all* labels to the callback routine. If
274              *  he wants to know if they matched the bsr, then he must
275              *  check the match_stat in the record */
276             ok = record_cb(dcr, rec);
277             rec->invalid = false;  /* The record was maybe invalid, but the next one is probably good */
278 #ifdef xxx
279             /*
280              * If this is the end of the Session (EOS) for this record
281              *  we can remove the record.  Note, there is a separate
282              *  record to read each session. If a new session is seen
283              *  a new record will be created at approx line 157 above.
284              * However, it seg faults in the for line at lineno 196.
285              */
286             if (rec->FileIndex == EOS_LABEL) {
287                Dmsg2(dbglvl, "Remove EOS rec. SI=%d ST=%d\n", rec->VolSessionId,
288                   rec->VolSessionTime);
289                recs->remove(rec);
290                free_record(rec);
291             }
292 #endif
293             continue;
294          } /* end if label record */
295 
296          /*
297           * Apply BSR filter
298           */
299          if (jcr->bsr) {
300             rec->match_stat = match_bsr(jcr->bsr, rec, &dev->VolHdr, &sessrec, jcr);
301             Dmsg2(dbglvl, "match_bsr=%d bsr->reposition=%d\n", rec->match_stat,
302                jcr->bsr->reposition);
303             if (rec->match_stat == -1) { /* no more possible matches */
304                done = true;   /* all items found, stop */
305                Dmsg1(dbglvl, "All done Addr=%s\n", dev->print_addr(ed1, sizeof(ed1)));
306                break;
307             } else if (rec->match_stat == 0) {  /* no match */
308                Dmsg3(dbglvl, "BSR no match: clear rem=%d FI=%d before set_eof pos %s\n",
309                   rec->remainder, rec->FileIndex, dev->print_addr(ed1, sizeof(ed1)));
310                rec->remainder = 0;
311                rec->state_bits &= ~REC_PARTIAL_RECORD;
312                if (try_repositioning(jcr, rec, dcr)) {
313                   break;              /* We moved on the volume, read next block */
314                }
315                continue;              /* we don't want record, read next one */
316             }
317          }
318          dcr->VolLastIndex = rec->FileIndex;  /* let caller know where we are */
319          if (is_partial_record(rec)) {
320             Dmsg6(dbglvl, "Partial, break. recno=%d state_bits=%s blk=%d SI=%d ST=%d FI=%d\n", record,
321                rec_state_bits_to_str(rec), block->BlockNumber,
322                rec->VolSessionId, rec->VolSessionTime, rec->FileIndex);
323             break;                    /* read second part of record */
324          }
325 
326          Dmsg6(dbglvl, "OK callback. recno=%d state_bits=%s blk=%d SI=%d ST=%d FI=%d\n", record,
327                rec_state_bits_to_str(rec), block->BlockNumber,
328                rec->VolSessionId, rec->VolSessionTime, rec->FileIndex);
329          if (lastFileIndex != no_FileIndex && lastFileIndex != rec->FileIndex) {
330             if (is_this_bsr_done(jcr, jcr->bsr, rec) && try_repositioning(jcr, rec, dcr)) {
331                Dmsg1(dbglvl, "This bsr done, break pos %s\n",
332                      dev->print_addr(ed1, sizeof(ed1)));
333                break;
334             }
335             Dmsg2(dbglvl, "==== inside LastIndex=%d FileIndex=%d\n", lastFileIndex, rec->FileIndex);
336          }
337          Dmsg2(dbglvl, "==== LastIndex=%d FileIndex=%d\n", lastFileIndex, rec->FileIndex);
338          lastFileIndex = rec->FileIndex;
339          if (rec->invalid) {
340             Dmsg5(0, "The record %d in block %ld SI=%ld ST=%ld FI=%ld was marked as invalid\n",
341                   rec->RecNum, rec->BlockNumber, rec->VolSessionId, rec->VolSessionTime, rec->FileIndex);
342          }
343          //ASSERTD(!rec->invalid || dcr->discard_invalid_records, "Got an invalid record");
344          ok = record_cb(dcr, rec);
345          rec->invalid = false;  /* The record was maybe invalid, but the next one is probably good */
346 #if 0
347          /*
348           * If we have a digest stream, we check to see if we have
349           *  finished the current bsr, and if so, repositioning will
350           *  be turned on.
351           */
352          if (crypto_digest_stream_type(rec->Stream) != CRYPTO_DIGEST_NONE) {
353             Dmsg3(dbglvl, "=== Have digest FI=%u before bsr check pos %s\n", rec->FileIndex,
354                   dev->print_addr(ed1, sizeof(ed1));
355             if (is_this_bsr_done(jcr, jcr->bsr, rec) && try_repositioning(jcr, rec, dcr)) {
356                Dmsg1(dbglvl, "==== BSR done at FI=%d\n", rec->FileIndex);
357                Dmsg1(dbglvl, "This bsr done, break pos=%s\n",
358                      dev->print_addr(ed1, sizeof(ed1)));
359                break;
360             }
361             Dmsg2(900, "After is_bsr_done pos %s\n", dev->print_addr(ed1, sizeof(ed1));
362          }
363 #endif
364       } /* end for loop over records */
365       Dmsg1(dbglvl, "After end recs in block. pos=%s\n", dev->print_addr(ed1, sizeof(ed1)));
366    } /* end for loop over blocks */
367 
368    /* Walk down list and free all remaining allocated recs */
369    while (!recs->empty()) {
370       rec = (DEV_RECORD *)recs->first();
371       recs->remove(rec);
372       free_record(rec);
373    }
374    delete recs;
375    print_block_read_errors(jcr, block);
376    return ok;
377 }
378 
379 /*
380  * See if we can reposition.
381  *   Returns:  true  if at end of volume
382  *             false otherwise
383  */
try_repositioning(JCR * jcr,DEV_RECORD * rec,DCR * dcr)384 static bool try_repositioning(JCR *jcr, DEV_RECORD *rec, DCR *dcr)
385 {
386    BSR *bsr;
387    DEVICE *dev = dcr->dev;
388    char ed1[50];
389 
390    bsr = find_next_bsr(jcr->bsr, dev);
391    Dmsg2(dbglvl, "nextbsr=%p mount_next_volume=%d\n", bsr, jcr->bsr->mount_next_volume);
392    if (bsr == NULL && jcr->bsr->mount_next_volume) {
393       Dmsg0(dbglvl, "Would mount next volume here\n");
394       Dmsg1(dbglvl, "Current position Addr=%s\n",
395          dev->print_addr(ed1, sizeof(ed1)));
396       jcr->bsr->mount_next_volume = false;
397       if (!dev->at_eot()) {
398          /* Set EOT flag to force mount of next Volume */
399          jcr->mount_next_volume = true;
400          dev->set_eot();
401       }
402       rec->Addr = 0;
403       return true;
404    }
405    if (bsr) {
406       /*
407        * ***FIXME*** gross kludge to make disk seeking work.  Remove
408        *   when find_next_bsr() is fixed not to return a bsr already
409        *   completed.
410        */
411       uint64_t dev_addr = dev->get_full_addr();
412       uint64_t bsr_addr = get_bsr_start_addr(bsr);
413 
414       /* Do not position backwards */
415       if (dev_addr > bsr_addr) {
416          return false;
417       }
418       Dmsg2(dbglvl, "Try_Reposition from addr=%llu to %llu\n",
419             dev_addr, bsr_addr);
420       dev->reposition(dcr, bsr_addr);
421       rec->Addr = 0;
422       return true;              /* We want the next block */
423    }
424    return false;
425 }
426 
427 /*
428  * Position to the first file on this volume
429  */
position_to_first_file(JCR * jcr,DCR * dcr,BSR * bsr)430 static BSR *position_to_first_file(JCR *jcr, DCR *dcr, BSR *bsr)
431 {
432    DEVICE *dev = dcr->dev;
433    uint64_t bsr_addr;
434    char ed1[50], ed2[50];
435 
436    Enter(150);
437    /*
438     * Now find and position to first file and block
439     *   on this tape.
440     */
441    if (bsr) {
442       bsr->reposition = true;    /* force repositioning */
443       bsr = find_next_bsr(bsr, dev);
444 
445       if ((bsr_addr=get_bsr_start_addr(bsr)) > 0) {
446          Jmsg(jcr, M_INFO, 0, _("Forward spacing Volume \"%s\" to addr=%s\n"),
447               dev->VolHdr.VolumeName, dev->print_addr(ed1, sizeof(ed1), bsr_addr));
448          dev->clear_eot();      /* TODO: See where to put this clear() exactly */
449          Dmsg2(dbglvl, "pos_to_first_file from addr=%s to %s\n",
450                dev->print_addr(ed1, sizeof(ed1)),
451                dev->print_addr(ed2, sizeof(ed2), bsr_addr));
452          dev->reposition(dcr, bsr_addr);
453       }
454    }
455    Leave(150);
456    return bsr;
457 }
458 
459 
handle_session_record(DEVICE * dev,DEV_RECORD * rec,SESSION_LABEL * sessrec)460 static void handle_session_record(DEVICE *dev, DEV_RECORD *rec, SESSION_LABEL *sessrec)
461 {
462    const char *rtype;
463    char buf[100];
464 
465    memset(sessrec, 0, sizeof(SESSION_LABEL));
466    switch (rec->FileIndex) {
467    case PRE_LABEL:
468       rtype = _("Fresh Volume Label");
469       break;
470    case VOL_LABEL:
471       rtype = _("Volume Label");
472       unser_volume_label(dev, rec);
473       break;
474    case SOS_LABEL:
475       rtype = _("Begin Session");
476       unser_session_label(sessrec, rec);
477       break;
478    case EOS_LABEL:
479       rtype = _("End Session");
480       break;
481    case EOM_LABEL:
482       rtype = _("End of Media");
483       break;
484    default:
485       bsnprintf(buf, sizeof(buf), _("Unknown code %d\n"), rec->FileIndex);
486       rtype = buf;
487       break;
488    }
489    Dmsg5(dbglvl, _("%s Record: VolSessionId=%d VolSessionTime=%d JobId=%d DataLen=%d\n"),
490          rtype, rec->VolSessionId, rec->VolSessionTime, rec->Stream, rec->data_len);
491 }
492 
493 #ifdef DEBUG
rec_state_bits_to_str(DEV_RECORD * rec)494 static char *rec_state_bits_to_str(DEV_RECORD *rec)
495 {
496    static char buf[200];
497    buf[0] = 0;
498    if (rec->state_bits & REC_NO_HEADER) {
499       bstrncat(buf, "Nohdr,", sizeof(buf));
500    }
501    if (is_partial_record(rec)) {
502       bstrncat(buf, "partial,", sizeof(buf));
503    }
504    if (rec->state_bits & REC_BLOCK_EMPTY) {
505       bstrncat(buf, "empty,", sizeof(buf));
506    }
507    if (rec->state_bits & REC_NO_MATCH) {
508       bstrncat(buf, "Nomatch,", sizeof(buf));
509    }
510    if (rec->state_bits & REC_CONTINUATION) {
511       bstrncat(buf, "cont,", sizeof(buf));
512    }
513    if (buf[0]) {
514       buf[strlen(buf)-1] = 0;
515    }
516    return buf;
517 }
518 #endif
519