]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/stored/btape.c
Make btape fill/unfill work
[bacula/bacula] / bacula / src / stored / btape.c
index e60ad718acf0870e5e3fa6d74342caf5b74d99dd..e6a671801eab23dceb80c0f83319cdbddb04cc93 100644 (file)
@@ -44,7 +44,7 @@ extern void free_config_resources();
 int quit = 0;
 char buf[100000];
 int bsize = TAPE_BSIZE;
-char VolName[100];
+char VolName[MAX_NAME_LENGTH];
 
 DEVICE *dev = NULL;
 DEVRES *device = NULL;
@@ -57,13 +57,16 @@ static void scancmd();
 static void rewindcmd();
 static void clearcmd();
 static void wrcmd();
+static void rrcmd();
 static void eodcmd();
 static void fillcmd();
+static void statcmd();
 static void unfillcmd();
 static int flush_block(DEV_BLOCK *block, int dump);
 static void record_cb(JCR *jcr, DEVICE *dev, DEV_BLOCK *block, DEV_RECORD *rec);
 static int my_mount_next_read_volume(JCR *jcr, DEVICE *dev, DEV_BLOCK *block);
 static void scan_blocks();
+static void set_volume_name(char *VolName, int volnum);
 
 
 /* Static variables */
@@ -80,7 +83,6 @@ static uint64_t VolBytes;
 static time_t now;
 static double kbs;
 static long file_index;
-static int verbose = 0;
 static int end_of_tape = 0;
 static uint32_t LastBlock = 0;
 static uint32_t eot_block;
@@ -91,9 +93,11 @@ static DEV_BLOCK *last_block = NULL;
 static DEV_BLOCK *this_block = NULL;
 static uint32_t last_file = 0;
 static uint32_t last_block_num = 0;
-static int simple = TRUE;
+static uint32_t BlockNumber = 0;
+static int simple = FALSE;
 
 static char *VolumeName = NULL;
+static int vol_num;
 
 static JCR *jcr = NULL;
 
@@ -499,6 +503,14 @@ static void capcmd()
    printf("%sSHORT ", dev->state & ST_SHORT ? "" : "!");
    printf("\n");
 
+   printf(_("Device parameters:\n"));
+   printf("Device name: %s\n", dev->dev_name);
+   printf("File=%u block=%u\n", dev->file, dev->block_num);
+   printf("Min block=%u Max block=%u\n", dev->min_block_size, dev->max_block_size);
+
+   printf("Status:\n");
+   statcmd();
+
 }
 
 /*
@@ -511,10 +523,10 @@ static void rectestcmd()
    DEV_RECORD *rec;
    int i, blkno = 0;
 
-   Pmsg0(0, "Test writting larger and larger records.\n\
-This is a torture test for records.\nI am going to write\n\
-larger and larger records. It will stop when the record size\n\
-plus the header exceeds the block size (by default about 64K\n");
+   Pmsg0(0, "Test writting larger and larger records.\n"
+"This is a torture test for records.\nI am going to write\n"
+"larger and larger records. It will stop when the record size\n"
+"plus the header exceeds the block size (by default about 64K)\n");
 
 
    get_cmd("Do you want to continue? (y/n): ");
@@ -546,6 +558,13 @@ plus the header exceeds the block size (by default about 64K\n");
    sm_check(__FILE__, __LINE__, False);
 }
 
+/*
+ * This test attempts to re-read a block written by Bacula
+ *   normally at the end of the tape. Bacula will then back up
+ *   over the two eof marks, backup over the record and reread
+ *   it to make sure it is valid.  Bacula can skip this validation
+ *   if you set "Backward space record = no"
+ */
 static int re_read_block_test()
 {
    DEV_BLOCK *block;
@@ -553,14 +572,17 @@ static int re_read_block_test()
    int stat = 0;
    int len;
 
-   if (!(dev->capabilities & CAP_EOM)) {
-      Pmsg0(-1, _("Skipping read backwards test because MT_EOM turned off.\n"));
+   if (!(dev->capabilities & CAP_BSR)) {
+      Pmsg0(-1, _("Skipping read backwards test because BSR turned off.\n"));
       return 0;
    }
 
-   Pmsg0(-1, _("\nWrite, backup, and re-read test.\n\n"
+   Pmsg0(-1, _("\n=== Write, backup, and re-read test ===\n\n"
       "I'm going to write three records and two eof's\n"
-      "then backup over the eof's and re-read the last record.\n\n"));
+      "then backup over the eof's and re-read the last record.\n"     
+      "Bacula does this after writing the last block on the\n"
+      "tape to verify that the block was written correctly.\n"
+      "It is not an *essential* feature ...\n\n")); 
    rewindcmd();
    block = new_block(dev);
    rec = new_record();
@@ -597,7 +619,7 @@ static int re_read_block_test()
       Pmsg0(0, _("Error writing block to device.\n")); 
       goto bail_out;
    } else {
-      Pmsg1(0, _("Wrote fourth record of %d bytes.\n"), rec->data_len);
+      Pmsg1(0, _("Wrote third record of %d bytes.\n"), rec->data_len);
    }
    weofcmd();
    weofcmd();
@@ -615,7 +637,7 @@ static int re_read_block_test()
       goto bail_out;
    }
    Pmsg0(0, "Backspace record OK.\n");
-   if (!read_block_from_dev(dev, block)) {
+   if (!read_block_from_dev(jcr, dev, block, NO_BLOCK_NUMBER_CHECK)) {
       Pmsg1(0, _("Read block failed! ERR=%s\n"), strerror(dev->dev_errno));
       goto bail_out;
    }
@@ -627,24 +649,38 @@ static int re_read_block_test()
    for (int i=0; i<len; i++) {
       if (rec->data[i] != 3) {
          Pmsg0(0, _("Bad data in record. Test failed!\n"));
-         Pmsg0(0, _("You might try adding:\n\n"
-               "Hardware End of File = No\n\n"
-               "to your Storage daemon's Device resource definition.\n"));
         goto bail_out;
       }
    }
-   Pmsg0(0, _("Re-read test succeeded!\n\n"));
+   Pmsg0(0, _("\nBlock re-read correct. Test succeeded!\n"));
+   Pmsg0(-1, _("=== End Write, backup, and re-read test ===\n\n"));
+
    stat = 1;
 
 bail_out:
    free_block(block);
    free_record(rec);
+   if (stat == 0) {
+      Pmsg0(0, _("This is not terribly serious since Bacula only uses\n"
+                 "this function to verify the last block written to the\n"
+                 "tape. Bacula will skip the last block verification\n"
+                 "if you add:\n\n"
+                  "Backward Space Record = No\n\n"
+                  "to your Storage daemon's Device resource definition.\n"));
+   }   
    return stat;
 }
 
+/*
+ * This test writes some records, then writes an end of file,
+ *   rewinds the tape, moves to the end of the data and attepts
+ *   to append to the tape.  This function is essential for
+ *   Bacula to be able to write multiple jobs to the tape.
+ */
 static int append_test()
 {
-   Pmsg0(-1, _("\n\n=== Append files test. ===\n\n"
+   Pmsg0(-1, _("\n\n=== Append files test ===\n\n"
+               "This test is essential to Bacula.\n\n"
 "I'm going to write one record  in file 0,\n"
 "                   two records in file 1,\n"
 "             and three records in file 2\n\n"));
@@ -661,78 +697,113 @@ static int append_test()
    rewindcmd();
    Pmsg0(0, _("Now moving to end of media.\n"));
    eodcmd();
-   Pmsg2(-1, _("End Append files test.\n\
-We should be in file 3. I am at file %d. This is %s\n\n"), 
+   Pmsg2(-1, _("We should be in file 3. I am at file %d. This is %s\n"), 
       dev->file, dev->file == 3 ? "correct!" : "NOT correct!!!!");
 
    if (dev->file != 3) {
-      Pmsg0(-1, _("To correct this problem, you might try adding:\n\n"
-            "Hardware End of Medium = No\n\n"
-            "to your Storage daemon's Device resource definition.\n"));
-      return 0;
+      return -1;
    }
 
-   Pmsg0(-1, _("\nNow I am going to attempt to append to the tape.\n"));
+   Pmsg0(-1, _("\nNow the important part, I am going to attempt to append to the tape.\n\n"));
    wrcmd(); 
    weofcmd();
    rewindcmd();
-   Pmsg0(0, _("Done writing, scanning results ...\n\n"));
+   Pmsg0(-1, _("Done appending, there should be no I/O errors\n\n"));
+   Pmsg0(-1, "Doing Bacula scan of blocks:\n");
    scan_blocks();
-   Pmsg0(-1, _("End Append to the tape test.\n\
-The above scan should have four files of:\n\
-One record, two records, three records, and one record \n\
-respectively each with 64,448 bytes.\n\n"));
-   Pmsg0(-1, _("If the above is not correct, you might try running Bacula\n"
-              "in fixed block mode by setting:\n\n"
-              "Minimum Block Size = nnn\n"
-              "Maximum Block Size = nnn\n\n"
-              "in your Storage daemon's Device definition.\n"
-              "nnn must match your tape driver's block size.\n"));
+   Pmsg0(-1, _("End scanning the tape.\n"));
+   Pmsg2(-1, _("We should be in file 4. I am at file %d. This is %s\n"), 
+      dev->file, dev->file == 4 ? "correct!" : "NOT correct!!!!");
 
-   return 1;
-}
+   if (dev->file != 4) {
+      return -2;
+   }
 
-void append_block_test()  
-{
-   Pmsg0(-1, "\n\n=== Append block test. ===\n\n\
-I'm going to write a block, an EOF, rewind, go to EOM,\n\
-then backspace over the EOF and attempt to append a second\n\
-block in the first file.\n\n");
-   rewindcmd();
-   wrcmd();
-   weofcmd();
-   rewindcmd();
-   eodcmd();
-   Pmsg2(-1, _("We should be at file 1. I am at EOM File=%d. This is %s\n"),
-      dev->file, dev->file == 1 ? "correct!" : "NOT correct!!!!");
-   Pmsg0(0, "Doing backspace file.\n");
-   bsfcmd();
-   Pmsg0(0, _("Write second block, hoping to append to first file.\n"));
-   wrcmd();
-   weofcmd();
-   rewindcmd();
-   Pmsg0(0, _("Done writing, scanning results ...\n\n"));
-   scan_blocks();
-   Pmsg0(-1, _("\nThe above should have one file of two blocks 64,448 bytes each.\n"));
+   return 1;
 }
 
-
-
 /* 
  * This is a general test of Bacula's functions
  *   needed to read and write the tape.
  */
 static void testcmd()
 {
-   re_read_block_test();
+   int stat;
 
-   if (!append_test()) {
-      return;
+   stat = append_test();
+   if (stat == 1) {                  /* OK get out */
+      goto all_done;
+   }
+   if (stat == -1) {                 /* first test failed */
+      if (dev_cap(dev, CAP_EOM)) {
+         Pmsg0(-1, "\nAppend test failed. Attempting again.\n"
+                   "Setting \"Hardware End of Medium = no\" and retrying append test.\n\n");
+        dev->capabilities &= ~CAP_EOM; /* turn off eom */
+        stat = append_test();
+        if (stat == 1) {
+            Pmsg0(-1, "\n\nIt looks like the test worked this time, please add:\n\n"
+                     "    Hardware End of Medium = No\n\n"
+                     "to your Device resource in the Storage conf file.\n");
+           goto all_done;
+        }
+        if (stat == -1) {
+            Pmsg0(-1, "\n\nThat appears not to have corrected the problem.\n");
+           goto all_done;
+        }
+        /* Wrong count after append */
+        if (stat == -2) {
+            Pmsg0(-1, "\n\nIt looks like the append failed. Attempting again.\n"
+                     "Setting \"BSF at EOM = yes\" and retrying append test.\n");
+           dev->capabilities |= CAP_BSFATEOM; /* backspace on eom */
+           stat = append_test();
+           if (stat == 1) {
+               Pmsg0(-1, "\n\nIt looks like the test worked this time, please add:\n\n"
+                     "    Hardware End of Medium = No\n"
+                     "    BSR at EOM = yes\n\n"
+                     "to your Device resource in the Storage conf file.\n");
+              goto all_done;
+           }
+        }
+
+         Pmsg0(-1, "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
+               "Unable to correct the problem. You MUST fix this\n"
+                "problem before Bacula can use your tape drive correctly\n");
+         Pmsg0(-1, "\nPerhaps running Bacula in fixed block mode will work.\n"
+               "Do so by setting:\n\n"
+               "Minimum Block Size = nnn\n"
+               "Maximum Block Size = nnn\n\n"
+               "in your Storage daemon's Device definition.\n"
+               "nnn must match your tape driver's block size.\n"
+               "This, however, is not really an ideal solution.\n");
+      }
    }
 
-   append_block_test();
+all_done:
+   Pmsg0(-1, _("\nThe above Bacula scan should have output identical to what follows.\n"
+        "Please double check it ...\n"
+        "=== Sample correct output ===\n"
+        "1 block of 64448 bytes in file 1\n"
+        "End of File mark.\n"
+        "2 blocks of 64448 bytes in file 2\n"
+        "End of File mark.\n"
+        "3 blocks of 64448 bytes in file 3\n"
+        "End of File mark.\n"
+        "1 block of 64448 bytes in file 4\n" 
+        "End of File mark.\n"
+        "Total files=4, blocks=7, bytes = 451136\n"
+        "=== End sample correct output ===\n\n"));
 
+   Pmsg0(-1, _("If the above scan output is not identical to the\n"
+               "sample output, you MUST correct the problem\n"
+               "or Bacula will not be able to write multiple Jobs to \n"
+               "the tape.\n\n"));
 
+   if (stat == 1) {
+      re_read_block_test();
+   }
+
+   Pmsg0(-1, _("\n=== End Append files test ===\n"));
+   
 }
 
 /* Forward space a file */
@@ -757,22 +828,6 @@ static void fsrcmd()
    Pmsg0(0, "Forward spaced one record.\n");
 }
 
-/* DEPRECATED DO NOT USE */
-static void rdcmd()
-{
-#ifdef xxxxx
-   int stat;
-
-   if (!read_dev(dev, buf, 512*126)) {
-      Pmsg1(0, "Bad status from read. ERR=%s\n", strerror_dev(dev));
-      return;
-   }
-   Pmsg1(10, "Read %d bytes\n", stat);
-#else
-   printf("Rdcmd no longer implemented.\n");
-#endif
-}
-
 
 /*
  * Write a Bacula block to the tape
@@ -813,6 +868,32 @@ bail_out:
    sm_check(__FILE__, __LINE__, False);
 }
 
+/* 
+ * Read a record from the tape
+ */
+static void rrcmd()
+{
+   char *buf;
+   int stat, len;
+
+   if (!get_cmd("Enter length to read: ")) {
+      return;
+   }
+   len = atoi(cmd);
+   if (len < 0 || len > 1000000) {
+      Pmsg0(0, _("Bad length entered, using default of 1024 bytes.\n"));
+      len = 1024;
+   }
+   buf = (char *)malloc(len);
+   stat = read(dev->fd, buf, len);
+   if (stat > 0 && stat <= len) {
+      errno = 0;
+   }
+   Pmsg3(0, _("Read of %d bytes gives stat=%d. ERR=%s\n"),
+      len, stat, strerror(errno));
+   free(buf);
+}
+
 
 /*
  * Scan tape by reading block by block. Report what is
@@ -904,7 +985,7 @@ static void scan_blocks()
    update_pos_dev(dev);
    tot_files = dev->file;
    for (;;) {
-      if (!read_block_from_device(dev, block)) {
+      if (!read_block_from_device(jcr, dev, block, NO_BLOCK_NUMBER_CHECK)) {
          Dmsg1(100, "!read_block(): ERR=%s\n", strerror_dev(dev));
         if (dev->state & ST_EOT) {
            if (blocks > 0) {
@@ -997,12 +1078,12 @@ static void fillcmd()
    DEV_RECORD rec;
    DEV_BLOCK  *block;
    char ec1[50];
-   char *p;
 
    ok = TRUE;
    stop = 0;
+   vol_num = 0;
 
-   Pmsg0(000, "\n\
+   Pmsg0(-1, "\n\
 This command simulates Bacula writing to a tape.\n\
 It requires either one or two blank tapes, which it\n\
 will label and write. It will print a status approximately\n\
@@ -1010,9 +1091,10 @@ every 322 MB, and write an EOF every 3.2 GB.  If you have\n\
 selected the simple test option, after writing the first tape\n\
 it will rewind it and re-read the last block written.\n\
 If you have selected the multiple tape test, when the first tape\n\
-fills, it will ask for a second, and after writing a few \n\
+fills, it will ask for a second, and after writing a few more \n\
 blocks, it will stop.  Then it will begin re-reading the\n\
-This may take a long time. I.e. hours! ...\n\n");
+two tapes.\n\n\
+This may take a long time -- hours! ...\n\n");
 
    get_cmd("Insert a blank tape then indicate if you want\n"
            "to run the simplified test (s) with one tape or\n"
@@ -1028,7 +1110,7 @@ This may take a long time. I.e. hours! ...\n\n");
       return;
    }
 
-   VolumeName = "TestVolume1";
+   set_volume_name("TestVolume1", 1);
    labelcmd();
    VolumeName = NULL;
 
@@ -1059,47 +1141,43 @@ This may take a long time. I.e. hours! ...\n\n");
         strerror_dev(dev));
       ok = FALSE;
    }
+   Pmsg0(-1, "Wrote Start Of Session label.\n");
 
    memset(&rec, 0, sizeof(rec));
    rec.data = get_memory(100000);     /* max record size */
-   /* 
-    * Fill write buffer with random data
-    */
+
 #define REC_SIZE 32768
-   p = rec.data;
-   for (int i=0; i < REC_SIZE; ) {
-      makeSessionKey(p, NULL, 0);
-      p += 16;
-      i += 16;
-   }
    rec.data_len = REC_SIZE;
 
    /* 
-    * Get Data from File daemon, write to device   
+    * Generate data as if from File daemon, write to device   
     */
-   jcr->VolFirstFile = 0;
+   jcr->VolFirstIndex = 0;
    time(&jcr->run_time);             /* start counting time for rates */
-   for (file_index = 0; ok && !job_cancelled(jcr); ) {
-      uint64_t *lp;
+   Pmsg0(-1, "Begin writing records to first tape ...\n");
+   for (file_index = 0; ok && !job_canceled(jcr); ) {
       rec.VolSessionId = jcr->VolSessionId;
       rec.VolSessionTime = jcr->VolSessionTime;
       rec.FileIndex = ++file_index;
       rec.Stream = STREAM_FILE_DATA;
 
-      /* Write file_index at beginning of buffer and add file_index to each
-       *  uint64_t item to make it unique.
+      /* 
+       * Fill the buffer with the file_index negated. Negation ensures that
+       *   more bits are turned on.
        */
-      lp = (uint64_t *)rec.data;
-      *lp++ = (uint64_t)file_index;
-      for (uint32_t i=0; i < (REC_SIZE-sizeof(uint64_t))/sizeof(uint64_t); i++) {
-        *lp++ = *lp + rec.FileIndex;
+      uint64_t *lp = (uint64_t *)rec.data;
+      for (uint32_t i=0; i < (rec.data_len-sizeof(uint64_t))/sizeof(uint64_t); i++) {
+        *lp++ = ~file_index;
       }
 
       Dmsg4(250, "before writ_rec FI=%d SessId=%d Strm=%s len=%d\n",
         rec.FileIndex, rec.VolSessionId, stream_to_ascii(rec.Stream, rec.FileIndex), 
         rec.data_len);
        
-      if (!write_record_to_block(block, &rec)) {
+      while (!write_record_to_block(block, &rec)) {
+        /*
+         * When we get here we have just filled a block
+         */
          Dmsg2(150, "!write_record_to_block data_len=%d rem=%d\n", rec.data_len,
                    rec.remainder);
 
@@ -1116,21 +1194,21 @@ This may take a long time. I.e. hours! ...\n\n");
            if (now <= 0) {
               now = 1;
            }
-           kbs = (double)dev->VolCatInfo.VolCatBytes / (1000 * now);
-            Pmsg3(000, "Wrote block=%u, VolBytes=%s rate=%.1f KB/s\n", block->BlockNumber,
+           kbs = (double)dev->VolCatInfo.VolCatBytes / (1000.0 * (double)now);
+            Pmsg3(-1, "Wrote block=%u, VolBytes=%s rate=%.1f KB/s\n", block->BlockNumber,
               edit_uint64_with_commas(dev->VolCatInfo.VolCatBytes, ec1), (float)kbs);
         }
-        /* Every 50000 blocks (approx 3.2MB) write an eof.
+        /* Every 15000 blocks (approx 1GB) write an EOF.
          */
-        if ((block->BlockNumber % 50000) == 0) {
-            Pmsg0(000, "Flush block, write EOF\n");
+        if ((block->BlockNumber % 15000) == 0) {
+            Pmsg0(-1, "Flush block, write EOF\n");
            flush_block(block, 0);
            weof_dev(dev, 1);
-           /* The weof resets the block number */
         }
 
-        if (block->BlockNumber > 10 && stop != 0) {      /* get out */
-           break;
+        /* Get out after writing 10 blocks to the second tape */
+        if (++BlockNumber > 10 && stop != 0) {      /* get out */
+           break;    
         }
       }
       if (!ok) {
@@ -1141,11 +1219,17 @@ This may take a long time. I.e. hours! ...\n\n");
       Dmsg4(190, "write_record FI=%s SessId=%d Strm=%s len=%d\n",
         FI_to_ascii(rec.FileIndex), rec.VolSessionId, 
         stream_to_ascii(rec.Stream, rec.FileIndex), rec.data_len);
+
+      /* Get out after writing 10 blocks to the second tape */
+      if (BlockNumber > 10 && stop != 0) {     /* get out */
+         Pmsg0(-1, "Done writing 10 blocks to second tape.\n");
+        break;    
+      }
    }
    if (stop > 0) {
-      Dmsg0(000, "Write_end_session_label()\n");
+      Dmsg0(100, "Write_end_session_label()\n");
       /* Create Job status for end of session label */
-      if (!job_cancelled(jcr) && ok) {
+      if (!job_canceled(jcr) && ok) {
         set_jcr_job_status(jcr, JS_Terminated);
       } else if (!ok) {
         set_jcr_job_status(jcr, JS_ErrorTerminated);
@@ -1156,20 +1240,21 @@ This may take a long time. I.e. hours! ...\n\n");
       }
       /* Write out final block of this session */
       if (!write_block_to_device(jcr, dev, block)) {
-         Pmsg0(000, _("Set ok=FALSE after write_block_to_device.\n"));
+         Pmsg0(-1, _("Set ok=FALSE after write_block_to_device.\n"));
         ok = FALSE;
       }
+      Pmsg0(-1, "Wrote End Of Session label.\n");
    }
 
    /* Release the device */
    if (!release_device(jcr, dev)) {
-      Pmsg0(000, _("Error in release_device\n"));
+      Pmsg0(-1, _("Error in release_device\n"));
       ok = FALSE;
    }
 
    free_block(block);
    free_memory(rec.data);
-   Pmsg0(000, _("\n\nDone filling tape. Now beginning re-read of tape ...\n"));
+   Pmsg0(-1, _("\n\nDone filling tape. Now beginning re-read of tape ...\n"));
 
    dump_block(last_block, _("Last block written to tape.\n"));
 
@@ -1195,18 +1280,17 @@ static void unfillcmd()
    dev->capabilities &= ~CAP_LABEL;   /* don't label anything here */
 
    end_of_tape = 0;
-   if (!simple) {
-      get_cmd(_("Mount first of two tapes. Press enter when ready: ")); 
-   }
+
+   get_cmd(_("Mount first tape. Press enter when ready: ")); 
    
    free_vol_list(jcr);
-   pm_strcpy(&jcr->VolumeName, "TestVolume1");
+   set_volume_name("TestVolume1", 1);
    jcr->bsr = NULL;
    create_vol_list(jcr);
    close_dev(dev);
    dev->state &= ~ST_READ;
    if (!acquire_device_for_read(jcr, dev, block)) {
-      Pmsg0(000, dev->errmsg);
+      Pmsg1(-1, "%s", dev->errmsg);
       return;
    }
 
@@ -1221,11 +1305,13 @@ static void unfillcmd()
        * Simplified test, we simply fsf to file, then read the
        * last block and make sure it is the same as the saved block.
        */
+      Pmsg0(000, "Rewinding tape ...\n");
       if (!rewind_dev(dev)) {
          Pmsg1(-1, _("Error rewinding: ERR=%s\n"), strerror_dev(dev));
         goto bail_out;
       }
       if (last_file > 0) {
+         Pmsg1(000, "Forward spacing to last file=%u\n", last_file);
         if (!fsf_dev(dev, last_file)) {
             Pmsg1(-1, _("Error in FSF: ERR=%s\n"), strerror_dev(dev));
            goto bail_out;
@@ -1235,7 +1321,7 @@ static void unfillcmd()
            last_file);
       Pmsg1(-1, _("Now reading to block %u.\n"), last_block_num);
       for (uint32_t i= 0; i < last_block_num; i++) {
-        if (!read_block_from_device(dev, block)) {
+        if (!read_block_from_device(jcr, dev, block, NO_BLOCK_NUMBER_CHECK)) {
             Pmsg1(-1, _("Error reading blocks: ERR=%s\n"), strerror_dev(dev));
             Pmsg2(-1, _("Wanted block %u error at block %u\n"), last_block_num, i);
            goto bail_out;
@@ -1244,11 +1330,13 @@ static void unfillcmd()
             Pmsg1(-1, _("At block %u\n"), i);
         }
       }
-      dump_block(last_block, _("Last block written"));
-      dump_block(block, _("Block read back"));
-      Pmsg0(-1, _("Except for the buffer address, the contents of\n"
-                  "the above two block dumps should be the same.\n"
-                  "If not you have a problem ...\n"));
+      if (last_block) {
+         dump_block(last_block, _("Last block written"));
+         dump_block(block, _("Block read back"));
+         Pmsg0(-1, _("Except for the buffer address, the contents of\n"
+                     "the above two block dumps should be the same.\n"
+                     "If not you have a problem ...\n"));
+      }
    }
 
 bail_out:
@@ -1259,17 +1347,16 @@ bail_out:
 
 
 /* 
- * We are called here from "unfill" for each record on the
- *   tape.
+ * We are called here from "unfill" for each record on the tape.
  */
 static void record_cb(JCR *jcr, DEVICE *dev, DEV_BLOCK *block, DEV_RECORD *rec)
 {
 
    SESSION_LABEL label;
    if (stop > 1) {                   /* on second tape */
-      Pmsg4(000, "Blk: FileIndex=%d: block=%u size=%d vol=%s\n", 
+      Pmsg4(-1, "Blk: FileIndex=%d: block=%u size=%d vol=%s\n", 
           rec->FileIndex, block->BlockNumber, block->block_len, dev->VolHdr.VolName);
-      Pmsg6(000, "   Rec: VId=%d VT=%d FI=%s Strm=%s len=%d state=%x\n",
+      Pmsg6(-1, "   Rec: VId=%d VT=%d FI=%s Strm=%s len=%d state=%x\n",
           rec->VolSessionId, rec->VolSessionTime, 
           FI_to_ascii(rec->FileIndex), stream_to_ascii(rec->Stream, rec->FileIndex),
           rec->data_len, rec->state);
@@ -1284,51 +1371,68 @@ static void record_cb(JCR *jcr, DEVICE *dev, DEV_BLOCK *block, DEV_RECORD *rec)
         dump_label_record(dev, rec, 1);
       }
       switch (rec->FileIndex) {
-        case PRE_LABEL:
-            Pmsg0(000, "Volume is prelabeled. This tape cannot be scanned.\n");
-           return;
-        case VOL_LABEL:
-           unser_volume_label(dev, rec);
-            Pmsg3(000, "VOL_LABEL: block=%u size=%d vol=%s\n", block->BlockNumber, 
-              block->block_len, dev->VolHdr.VolName);
-           stop++;
-           break;
-        case SOS_LABEL:
-           unser_session_label(&label, rec);
-            Pmsg1(000, "SOS_LABEL: JobId=%u\n", label.JobId);
-           break;
-        case EOS_LABEL:
-           unser_session_label(&label, rec);
-            Pmsg2(000, "EOS_LABEL: block=%u JobId=%u\n", block->BlockNumber, 
-              label.JobId);
-           break;
-        case EOM_LABEL:
-            Pmsg0(000, "EOM_LABEL:\n");
-           break;
-        case EOT_LABEL:              /* end of all tapes */
-           char ec1[50];
+      case PRE_LABEL:
+         Pmsg0(-1, "Volume is prelabeled. This tape cannot be scanned.\n");
+        return;
+      case VOL_LABEL:
+        unser_volume_label(dev, rec);
+         Pmsg3(-1, "VOL_LABEL: block=%u size=%d vol=%s\n", block->BlockNumber, 
+           block->block_len, dev->VolHdr.VolName);
+        stop++;
+        break;
+      case SOS_LABEL:
+        unser_session_label(&label, rec);
+         Pmsg1(-1, "SOS_LABEL: JobId=%u\n", label.JobId);
+        break;
+      case EOS_LABEL:
+        unser_session_label(&label, rec);
+         Pmsg2(-1, "EOS_LABEL: block=%u JobId=%u\n", block->BlockNumber, 
+           label.JobId);
+        break;
+      case EOM_LABEL:
+         Pmsg0(-1, "EOM_LABEL:\n");
+        break;
+      case EOT_LABEL:             /* end of all tapes */
+        char ec1[50];
 
-           if (LastBlock != block->BlockNumber) {
-              VolBytes += block->block_len;
-           }
-           LastBlock = block->BlockNumber;
-           now = time(NULL);
-           now -= jcr->run_time;
-           if (now <= 0) {
-              now = 1;
-           }
-           kbs = (double)VolBytes / (1000 * now);
-            Pmsg3(000, "Read block=%u, VolBytes=%s rate=%.1f KB/s\n", block->BlockNumber,
-                    edit_uint64_with_commas(VolBytes, ec1), (float)kbs);
+        if (LastBlock != block->BlockNumber) {
+           VolBytes += block->block_len;
+        }
+        LastBlock = block->BlockNumber;
+        now = time(NULL);
+        now -= jcr->run_time;
+        if (now <= 0) {
+           now = 1;
+        }
+        kbs = (double)VolBytes / (1000 * now);
+         Pmsg3(000, "Read block=%u, VolBytes=%s rate=%.1f KB/s\n", block->BlockNumber,
+                 edit_uint64_with_commas(VolBytes, ec1), (float)kbs);
 
-            Pmsg0(000, "End of all tapes.\n");
+         Pmsg0(000, "End of all tapes.\n");
 
-           break;
-        default:
-           break;
+        break;
+      default:
+        break;
       }
       return;
    }
+   if (++file_index != rec->FileIndex) {
+      Pmsg3(000, "Incorrect FileIndex in Block %u. Got %d, expected %d.\n", 
+        block->BlockNumber, rec->FileIndex, file_index);
+   }
+   /*
+    * Now check that the right data is in the record.
+    */
+   uint64_t *lp = (uint64_t *)rec->data;
+   uint64_t val = ~file_index;
+   for (uint32_t i=0; i < (REC_SIZE-sizeof(uint64_t))/sizeof(uint64_t); i++) {
+      if (*lp++ != val) {
+         Pmsg2(000, "Record %d contains bad data in Block %u.\n",
+           file_index, block->BlockNumber);
+        break;
+      }
+   }
+
    if (LastBlock != block->BlockNumber) {
       VolBytes += block->block_len;
    }
@@ -1367,12 +1471,9 @@ static int flush_block(DEV_BLOCK *block, int dump)
       this_block = new_block(dev);
    }
    /* Copy block */
+   free_memory(this_block->buf);    
    memcpy(this_block, block, sizeof(DEV_BLOCK));
-   if (this_block->buf_len < block->buf_len) {
-      free_memory(this_block->buf);    
-      this_block->buf = get_memory(block->buf_len);
-      this_block->buf_len = block->buf_len;
-   }
+   this_block->buf = get_memory(block->buf_len);
    memcpy(this_block->buf, block->buf, this_block->buf_len);
    this_file = dev->file;
    this_block_num = dev->block_num;
@@ -1409,6 +1510,7 @@ static int flush_block(DEV_BLOCK *block, int dump)
            return 0;
         }
         stop = 1;                                                     
+        BlockNumber = 0;             /* start counting for second tape */
       }
       unlock_device(dev);
       return 1;                      /* end of tape reached */
@@ -1446,7 +1548,6 @@ static struct cmdstruct commands[] = {
  {"label",      labelcmd,     "write a Bacula label to the tape"},
  {"load",       loadcmd,      "load a tape"},
  {"quit",       quitcmd,      "quit btape"},   
- {"rd",         rdcmd,        "read tape"},
  {"readlabel",  readlabelcmd, "read and print the Bacula tape label"},
  {"rectest",    rectestcmd,   "test record handling functions"},
  {"rewind",     rewindcmd,    "rewind the tape"},
@@ -1454,7 +1555,8 @@ static struct cmdstruct commands[] = {
  {"status",     statcmd,      "print tape status"},
  {"test",       testcmd,      "General test Bacula tape functions"},
  {"weof",       weofcmd,      "write an EOF on the tape"},
- {"wr",         wrcmd,        "write a single record of 2048 bytes"}, 
+ {"wr",         wrcmd,        "write a single Bacula block"}, 
+ {"rr",         rrcmd,        "read a single record"},
             };
 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
 
@@ -1539,16 +1641,21 @@ get_cmd(char *prompt)
 
 /* Dummies to replace askdir.c */
 int    dir_get_volume_info(JCR *jcr, int writing) { return 1;}
-int    dir_find_next_appendable_volume(JCR *jcr) { return 1;}
 int    dir_update_volume_info(JCR *jcr, VOLUME_CAT_INFO *vol, int relabel) { return 1; }
 int    dir_create_jobmedia_record(JCR *jcr) { return 1; }
 int    dir_update_file_attributes(JCR *jcr, DEV_RECORD *rec) { return 1;}
 int    dir_send_job_status(JCR *jcr) {return 1;}
 
 
+
+int    dir_find_next_appendable_volume(JCR *jcr) 
+{ 
+   return 1; 
+}
+
 int dir_ask_sysop_to_mount_volume(JCR *jcr, DEVICE *dev)
 {
-   Pmsg0(000, dev->errmsg);          /* print reason */
+   Pmsg1(-1, "%s", dev->errmsg);           /* print reason */
    fprintf(stderr, "Mount Volume \"%s\" on device %s and press return when ready: ",
       jcr->VolumeName, dev_name(dev));
    getchar();  
@@ -1560,9 +1667,11 @@ int dir_ask_sysop_to_mount_next_volume(JCR *jcr, DEVICE *dev)
    fprintf(stderr, "Mount next Volume on device %s and press return when ready: ",
       dev_name(dev));
    getchar();  
-   VolumeName = "TestVolume2";
+   set_volume_name("TestVolume2", 2);
    labelcmd();
    VolumeName = NULL;
+   BlockNumber = 0;
+   stop = 1;
    return 1;
 }
 
@@ -1581,8 +1690,8 @@ static int my_mount_next_read_volume(JCR *jcr, DEVICE *dev, DEV_BLOCK *block)
    if (now <= 0) {
       now = 1;
    }
-   kbs = (double)VolBytes / (1000 * now);
-   Pmsg3(000, "Read block=%u, VolBytes=%s rate=%.1f KB/s\n", block->BlockNumber,
+   kbs = (double)VolBytes / (1000.0 * (double)now);
+   Pmsg3(-1, "Read block=%u, VolBytes=%s rate=%.1f KB/s\n", block->BlockNumber,
            edit_uint64_with_commas(VolBytes, ec1), (float)kbs);
 
    if (strcmp(jcr->VolumeName, "TestVolume2") == 0) {
@@ -1591,7 +1700,7 @@ static int my_mount_next_read_volume(JCR *jcr, DEVICE *dev, DEV_BLOCK *block)
    }
 
    free_vol_list(jcr);
-   pm_strcpy(&jcr->VolumeName, "TestVolume2");
+   set_volume_name("TestVolume2", 2);
    jcr->bsr = NULL;
    create_vol_list(jcr);
    close_dev(dev);
@@ -1602,3 +1711,11 @@ static int my_mount_next_read_volume(JCR *jcr, DEVICE *dev, DEV_BLOCK *block)
    }
    return 1;                      /* next volume mounted */
 }
+
+static void set_volume_name(char *VolName, int volnum) 
+{
+   VolumeName = VolName;
+   vol_num = volnum;
+   pm_strcpy(&jcr->VolumeName, VolName);
+   bstrncpy(dev->VolCatInfo.VolCatName, VolName, sizeof(dev->VolCatInfo.VolCatName));
+}