]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/stored/btape.c
Backport more master code
[bacula/bacula] / bacula / src / stored / btape.c
index d966a881f0939afbbc07e9571f11fd02cda8bdab..967887290d4de97d85527c210001ec3654b60246 100644 (file)
@@ -1,12 +1,12 @@
 /*
    Bacula® - The Network Backup Solution
 
-   Copyright (C) 2000-2008 Free Software Foundation Europe e.V.
+   Copyright (C) 2000-2012 Free Software Foundation Europe e.V.
 
    The main author of Bacula is Kern Sibbald, with contributions from
    many others, a complete list can be found in the file AUTHORS.
    This program is Free Software; you can redistribute it and/or
-   modify it under the terms of version two of the GNU General Public
+   modify it under the terms of version three of the GNU Affero General Public
    License as published by the Free Software Foundation and included
    in the file LICENSE.
 
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    General Public License for more details.
 
-   You should have received a copy of the GNU General Public License
+   You should have received a copy of the GNU Affero General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
 
-   Bacula® is a registered trademark of John Walker.
+   Bacula® is a registered trademark of Kern Sibbald.
    The licensor of Bacula is the Free Software Foundation Europe
    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
    Switzerland, email:ftf@fsfeurope.org.
  *   Note, this program reads stored.conf, and will only
  *     talk to devices that are configured.
  *
- *   Version $Id$
- *
  */
 
 #include "bacula.h"
 #include "stored.h"
 
+#ifdef USE_VTAPE
+#include "vtape.h"
+#endif
+
 /* Dummy functions */
 int generate_daemon_event(JCR *jcr, const char *event) { return 1; }
+extern bool parse_sd_config(CONFIG *config, const char *configfile, int exit_code);
 
 /* External subroutines */
 extern void free_config_resources();
@@ -69,7 +72,9 @@ static uint32_t btape_state_level = 2;
 DEVICE *dev = NULL;
 DCR *dcr;
 DEVRES *device = NULL;
+int exit_code = 0;
 
+#define REC_SIZE 32768
 
 /* Forward referenced subroutines */
 static void do_tape_cmds();
@@ -94,10 +99,11 @@ static void set_volume_name(const char *VolName, int volnum);
 static void rawfill_cmd();
 static bool open_the_device();
 static void autochangercmd();
-static void do_unfill();
+static bool do_unfill();
 
 
 /* Static variables */
+static CONFIG *config;
 #define CONFIG_FILE "bacula-sd.conf"
 char *configfile = NULL;
 
@@ -117,7 +123,6 @@ static int stop = 0;
 static uint64_t vol_size;
 static uint64_t VolBytes;
 static time_t now;
-static double kbs;
 static int32_t file_index;
 static int end_of_tape = 0;
 static uint32_t LastBlock = 0;
@@ -166,6 +171,7 @@ int main(int margc, char *margv[])
    bindtextdomain("bacula", LOCALEDIR);
    textdomain("bacula");
    init_stack_dump();
+   lmgr_init_thread();
 
    /* Sanity checks */
    if (TAPE_BSIZE % B_DEV_BSIZE != 0 || TAPE_BSIZE / B_DEV_BSIZE == 0) {
@@ -265,8 +271,8 @@ int main(int margc, char *margv[])
 
    daemon_start_time = time(NULL);
 
-   parse_config(configfile);
-
+   config = new_config_parser();
+   parse_sd_config(config, configfile, M_ERROR_TERM);
 
    /* See if we can open a device */
    if (margc == 0) {
@@ -299,25 +305,30 @@ int main(int margc, char *margv[])
    }
    dcr = jcr->dcr;
    if (!open_the_device()) {
-      goto terminate;
+      exit(1);
    }
 
    Dmsg0(200, "Do tape commands\n");
    do_tape_cmds();
 
-terminate:
-   terminate_btape(0);
-   return 0;
+   terminate_btape(exit_code);
 }
 
 static void terminate_btape(int stat)
 {
 
-   sm_check(__FILE__, __LINE__, false);
+   Dsm_check(200);
+   free_jcr(jcr);
+   jcr = NULL;
+
    if (configfile) {
       free(configfile);
    }
-   free_config_resources();
+   if (config) {
+      config->free_resources();
+      free(config);
+      config = NULL;
+   }
    if (args) {
       free_pool_memory(args);
       args = NULL;
@@ -331,15 +342,13 @@ static void terminate_btape(int stat)
       free_bsr(bsr);
    }
 
-   free_jcr(jcr);
-   jcr = NULL;
+
+   free_volume_lists();
 
    if (dev) {
       dev->term();
    }
 
-   free_volume_list();
-
    if (debug_level > 10)
       print_memory_pool_stats();
 
@@ -349,13 +358,109 @@ static void terminate_btape(int stat)
 
    stop_watchdog();
    term_msg();
-   close_memory_pool();               /* free memory in pool */
    term_last_jobs_list();
+   close_memory_pool();               /* free memory in pool */
+   lmgr_cleanup_main();
 
    sm_dump(false);
    exit(stat);
 }
 
+
+btime_t total_time=0;
+uint64_t total_size=0;
+
+static void init_total_speed()
+{
+   total_size = 0;
+   total_time = 0;
+}
+
+static void print_total_speed()
+{
+   char ec1[50], ec2[50];
+   uint64_t rate = total_size / total_time;
+   Pmsg2(000, _("Total Volume bytes=%sB. Total Write rate = %sB/s\n"),
+         edit_uint64_with_suffix(total_size, ec1), 
+         edit_uint64_with_suffix(rate, ec2));
+}
+
+static void init_speed()
+{
+   time(&jcr->run_time);              /* start counting time for rates */
+   jcr->JobBytes=0;
+}
+
+static void print_speed(uint64_t bytes)
+{
+   char ec1[50], ec2[50];
+   uint64_t rate;
+
+   now = time(NULL);
+   now -= jcr->run_time;
+   if (now <= 0) {
+      now = 1;                     /* don't divide by zero */
+   }
+
+   total_time += now;
+   total_size += bytes;
+
+   rate = bytes / now;
+   Pmsg2(000, _("Volume bytes=%sB. Write rate = %sB/s\n"),
+         edit_uint64_with_suffix(bytes, ec1),
+         edit_uint64_with_suffix(rate, ec2));
+}
+
+/*
+ * Helper that fill a buffer with random data or not
+ */
+typedef enum {
+   FILL_RANDOM,
+   FILL_ZERO
+} fill_mode_t;
+
+static void fill_buffer(fill_mode_t mode, char *buf, uint32_t len)
+{
+   int fd;
+   switch (mode) {
+   case FILL_RANDOM:
+      fd = open("/dev/urandom", O_RDONLY);
+      if (fd != -1) {
+         read(fd, buf, len);
+         close(fd);
+      } else {
+         uint32_t *p = (uint32_t *)buf;
+         srandom(time(NULL));
+         for (uint32_t i=0; i<len/sizeof(uint32_t); i++) {
+            p[i] = random();
+         }
+      }
+      break;
+
+   case FILL_ZERO:
+      memset(buf, 0xFF, len);
+      break;
+
+   default:
+      ASSERT(0);
+   }
+}
+
+static void mix_buffer(fill_mode_t mode, char *data, uint32_t len)
+{
+   uint32_t i;
+   uint32_t *lp = (uint32_t *)data;
+
+   if (mode == FILL_ZERO) {
+      return;
+   }
+
+   lp[0] += lp[13];
+   for (i=1; i < (len-sizeof(uint32_t))/sizeof(uint32_t)-1; i+=100) {
+      lp[i] += lp[0];
+   }
+}
+
 static bool open_the_device()
 {
    DEV_BLOCK *block;
@@ -364,7 +469,7 @@ static bool open_the_device()
    block = new_block(dev);
    dev->r_dlock();
    Dmsg1(200, "Opening device %s\n", dcr->VolumeName);
-   if (dev->open(dcr, OPEN_READ_WRITE) < 0) {
+   if (!dev->open(dcr, OPEN_READ_WRITE)) {
       Emsg1(M_FATAL, 0, _("dev open failed: %s\n"), dev->errmsg);
       ok = false;
       goto bail_out;
@@ -403,7 +508,6 @@ static void labelcmd()
       }
    }
    dev->rewind(dcr);
-   dev->weof(1);
    write_new_volume_label_to_dev(dcr, cmd, "Default", false,/*no relabel*/ true /* label dvd now */);
    Pmsg1(-1, _("Wrote Volume label for volume \"%s\".\n"), cmd);
 }
@@ -624,16 +728,16 @@ static void capcmd()
 }
 
 /*
- * Test writting larger and larger records.
+ * Test writing larger and larger records.
  * This is a torture test for records.
  */
 static void rectestcmd()
 {
-   DEV_BLOCK *block;
+   DEV_BLOCK *save_block;
    DEV_RECORD *rec;
    int i, blkno = 0;
 
-   Pmsg0(0, _("Test writting larger and larger records.\n"
+   Pmsg0(0, _("Test writing 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"));
@@ -645,27 +749,29 @@ static void rectestcmd()
       return;
    }
 
-   sm_check(__FILE__, __LINE__, false);
-   block = new_block(dev);
+   Dsm_check(200);
+   save_block = dcr->block;
+   dcr->block = new_block(dev);
    rec = new_record();
 
    for (i=1; i<500000; i++) {
       rec->data = check_pool_memory_size(rec->data, i);
       memset(rec->data, i & 0xFF, i);
       rec->data_len = i;
-      sm_check(__FILE__, __LINE__, false);
-      if (write_record_to_block(block, rec)) {
-         empty_block(block);
+      Dsm_check(200);
+      if (write_record_to_block(dcr, rec)) {
+         empty_block(dcr->block);
          blkno++;
          Pmsg2(0, _("Block %d i=%d\n"), blkno, i);
       } else {
          break;
       }
-      sm_check(__FILE__, __LINE__, false);
+      Dsm_check(200);
    }
    free_record(rec);
-   free_block(block);
-   sm_check(__FILE__, __LINE__, false);
+   free_block(dcr->block);
+   dcr->block = save_block;     /* restore block to dcr */
+   Dsm_check(200);
 }
 
 /*
@@ -675,16 +781,16 @@ static void rectestcmd()
  *   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()
+static bool re_read_block_test()
 {
    DEV_BLOCK *block = dcr->block;
    DEV_RECORD *rec;
-   int stat = 0;
+   bool rc = false;
    int len;
 
    if (!(dev->capabilities & CAP_BSR)) {
       Pmsg0(-1, _("Skipping read backwards test because BSR turned off.\n"));
-      return 0;
+      return true;
    }
 
    Pmsg0(-1, _("\n=== Write, backup, and re-read test ===\n\n"
@@ -699,33 +805,33 @@ static int re_read_block_test()
    rec->data = check_pool_memory_size(rec->data, block->buf_len);
    len = rec->data_len = block->buf_len-100;
    memset(rec->data, 1, rec->data_len);
-   if (!write_record_to_block(block, rec)) {
+   if (!write_record_to_block(dcr, rec)) {
       Pmsg0(0, _("Error writing record to block.\n"));
       goto bail_out;
    }
-   if (!write_block_to_dev(dcr)) {
+   if (!dcr->write_block_to_dev()) {
       Pmsg0(0, _("Error writing block to device.\n"));
       goto bail_out;
    } else {
       Pmsg1(0, _("Wrote first record of %d bytes.\n"), rec->data_len);
    }
    memset(rec->data, 2, rec->data_len);
-   if (!write_record_to_block(block, rec)) {
+   if (!write_record_to_block(dcr, rec)) {
       Pmsg0(0, _("Error writing record to block.\n"));
       goto bail_out;
    }
-   if (!write_block_to_dev(dcr)) {
+   if (!dcr->write_block_to_dev()) {
       Pmsg0(0, _("Error writing block to device.\n"));
       goto bail_out;
    } else {
       Pmsg1(0, _("Wrote second record of %d bytes.\n"), rec->data_len);
    }
    memset(rec->data, 3, rec->data_len);
-   if (!write_record_to_block(block, rec)) {
+   if (!write_record_to_block(dcr, rec)) {
       Pmsg0(0, _("Error writing record to block.\n"));
       goto bail_out;
    }
-   if (!write_block_to_dev(dcr)) {
+   if (!dcr->write_block_to_dev()) {
       Pmsg0(0, _("Error writing block to device.\n"));
       goto bail_out;
    } else {
@@ -751,13 +857,13 @@ static int re_read_block_test()
       goto bail_out;
    }
    Pmsg0(0, _("Backspace record OK.\n"));
-   if (!read_block_from_dev(dcr, NO_BLOCK_NUMBER_CHECK)) {
+   if (!dcr->read_block_from_dev(NO_BLOCK_NUMBER_CHECK)) {
       berrno be;
       Pmsg1(0, _("Read block failed! ERR=%s\n"), be.bstrerror(dev->dev_errno));
       goto bail_out;
    }
    memset(rec->data, 0, rec->data_len);
-   if (!read_record_from_block(dcr, block, rec)) {
+   if (!read_record_from_block(dcr, rec)) {
       berrno be;
       Pmsg1(0, _("Read block failed! ERR=%s\n"), be.bstrerror(dev->dev_errno));
       goto bail_out;
@@ -771,11 +877,11 @@ static int re_read_block_test()
    Pmsg0(0, _("\nBlock re-read correct. Test succeeded!\n"));
    Pmsg0(-1, _("=== End Write, backup, and re-read test ===\n\n"));
 
-   stat = 1;
+   rc = true;
 
 bail_out:
    free_record(rec);
-   if (stat == 0) {
+   if (!rc) {
       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"
@@ -783,85 +889,344 @@ bail_out:
                   "Backward Space Record = No\n\n"
                   "to your Storage daemon's Device resource definition.\n"));
    }
-   return stat;
+   return rc;
 }
 
+static bool speed_test_raw(fill_mode_t mode, uint64_t nb_gb, uint32_t nb)
+{
+   DEV_BLOCK *block = dcr->block;
+   int stat;
+   uint32_t block_num = 0;
+   int my_errno;
+   char ed1[200];
+   nb_gb *= 1024*1024*1024;      /* convert size from nb to GB */
+
+   init_total_speed();
+   fill_buffer(mode, block->buf, block->buf_len);
+
+   Pmsg3(0, _("Begin writing %i files of %sB with raw blocks of %u bytes.\n"), 
+         nb, edit_uint64_with_suffix(nb_gb, ed1), block->buf_len);
+
+   for (uint32_t j=0; j<nb; j++) {
+      init_speed();
+      for ( ;jcr->JobBytes < nb_gb; ) {
+         stat = dev->d_write(dev->fd(), block->buf, block->buf_len);
+         if (stat == (int)block->buf_len) {
+            if ((block_num++ % 500) == 0) {
+               printf("+");
+               fflush(stdout);
+            }
+
+            mix_buffer(mode, block->buf, block->buf_len);
+
+            jcr->JobBytes += stat;
+
+         } else {
+            my_errno = errno;
+            printf("\n");
+            berrno be;
+            printf(_("Write failed at block %u. stat=%d ERR=%s\n"), block_num,
+                   stat, be.bstrerror(my_errno));
+            return false;
+         }
+      }
+      printf("\n");
+      weofcmd();
+      print_speed(jcr->JobBytes);
+   }
+   print_total_speed();
+   printf("\n");
+   return true;
+}
+
+
+static bool speed_test_bacula(fill_mode_t mode, uint64_t nb_gb, uint32_t nb)
+{
+   DEV_BLOCK *block = dcr->block;
+   char ed1[200];
+   DEV_RECORD *rec;
+   uint64_t last_bytes = dev->VolCatInfo.VolCatBytes;
+   uint64_t written=0;
+
+   nb_gb *= 1024*1024*1024;      /* convert size from nb to GB */
+
+   init_total_speed();
+
+   empty_block(block);
+   rec = new_record();
+   rec->data = check_pool_memory_size(rec->data, block->buf_len);
+   rec->data_len = block->buf_len-100;
+
+   fill_buffer(mode, rec->data, rec->data_len);
+
+   Pmsg3(0, _("Begin writing %i files of %sB with blocks of %u bytes.\n"), 
+         nb, edit_uint64_with_suffix(nb_gb, ed1), block->buf_len);
+
+   for (uint32_t j=0; j<nb; j++) {
+      written = 0;
+      init_speed();
+      for ( ; written < nb_gb; ) {
+
+         if (!write_record_to_block(dcr, rec)) {
+            Pmsg0(0, _("\nError writing record to block.\n"));
+            goto bail_out;
+         }
+         if (!dcr->write_block_to_dev()) {
+            Pmsg0(0, _("\nError writing block to device.\n"));
+            goto bail_out;
+         }
+
+         if ((block->BlockNumber % 500) == 0) {
+            printf("+");
+            fflush(stdout);
+         }
+         written += dev->VolCatInfo.VolCatBytes - last_bytes;
+         last_bytes = dev->VolCatInfo.VolCatBytes;
+         mix_buffer(mode, rec->data, rec->data_len);
+      }
+      printf("\n");
+      weofcmd();
+      print_speed(written);
+   }
+   print_total_speed();
+   printf("\n");
+   free_record(rec);
+   return true;
+
+bail_out:
+   free_record(rec);
+   return false;
+}
+
+/* TODO: use UAContext */
+static int btape_find_arg(const char *keyword)
+{
+   for (int i=1; i<argc; i++) {
+      if (strcasecmp(keyword, argk[i]) == 0) {
+         return i;
+      }
+   }
+   return -1;
+}
+
+#define ok(a)    if (!(a)) return
 
 /*
- * This test writes Bacula blocks to the tape in
- *   several files. It then rewinds the tape and attepts
- *   to read these blocks back checking the data.
+ * For file (/dev/zero, /dev/urandom, normal?) 
+ *    use raw mode to write a suite of 3 files of 1, 2, 4, 8 GB
+ *    use qfill mode to write the same
+ * 
  */
-static int write_read_test()
+static void speed_test()
+{
+   bool do_zero=true, do_random=true, do_block=true, do_raw=true;
+   uint32_t file_size=0, nb_file=3;
+   int32_t i;
+
+   i = btape_find_arg("file_size");
+   if (i > 0) {
+      file_size = atoi(argv[i]);
+      if (file_size > 100) {
+         Pmsg0(0, _("The file_size is too big, stop this test with Ctrl-c.\n"));
+      }
+   }
+
+   i = btape_find_arg("nb_file");
+   if (i > 0) {
+      nb_file = atoi(argv[i]);
+   }
+
+   if (btape_find_arg("skip_zero") > 0) {
+      do_zero = false;
+   }
+
+   if (btape_find_arg("skip_random") > 0) {
+      do_random = false;
+   }
+
+   if (btape_find_arg("skip_raw") > 0) {
+      do_raw = false;
+   }
+
+   if (btape_find_arg("skip_block") > 0) {
+      do_block = false;
+   }
+
+   if (do_raw) {
+      dev->rewind(dcr);
+      if (do_zero) { 
+         Pmsg0(0, _("Test with zero data, should give the "
+                    "maximum throughput.\n"));
+         if (file_size) {
+            ok(speed_test_raw(FILL_ZERO, file_size, nb_file));
+         } else {
+            ok(speed_test_raw(FILL_ZERO, 1, nb_file));
+            ok(speed_test_raw(FILL_ZERO, 2, nb_file));
+            ok(speed_test_raw(FILL_ZERO, 4, nb_file));
+         }
+      }
+
+      if (do_random) { 
+         Pmsg0(0, _("Test with random data, should give the minimum "
+                    "throughput.\n"));
+         if (file_size) {
+            ok(speed_test_raw(FILL_RANDOM, file_size, nb_file));
+         } else {
+            ok(speed_test_raw(FILL_RANDOM, 1, nb_file));
+            ok(speed_test_raw(FILL_RANDOM, 2, nb_file));
+            ok(speed_test_raw(FILL_RANDOM, 4, nb_file));
+         }
+      }
+   }
+
+   if (do_block) {
+      dev->rewind(dcr);
+      if (do_zero) {
+         Pmsg0(0, _("Test with zero data and bacula block structure.\n"));
+         if (file_size) {
+            ok(speed_test_bacula(FILL_ZERO, file_size, nb_file));
+         } else { 
+            ok(speed_test_bacula(FILL_ZERO, 1, nb_file));
+            ok(speed_test_bacula(FILL_ZERO, 2, nb_file));
+               ok(speed_test_bacula(FILL_ZERO, 4, nb_file));
+         }
+      }
+   
+      if (do_random) { 
+         Pmsg0(0, _("Test with random data, should give the minimum "
+                    "throughput.\n"));
+         if (file_size) {
+            ok(speed_test_bacula(FILL_RANDOM, file_size, nb_file));
+         } else {
+            ok(speed_test_bacula(FILL_RANDOM, 1, nb_file));
+            ok(speed_test_bacula(FILL_RANDOM, 2, nb_file));
+            ok(speed_test_bacula(FILL_RANDOM, 4, nb_file));
+         }
+      }
+   }
+}
+
+const int num_recs = 10000;
+
+static bool write_two_files()
 {
    DEV_BLOCK *block;
    DEV_RECORD *rec;
-   int stat = 0;
    int len, i, j;
    int *p;
+   bool rc = false;       /* bad return code */
+   DEVICE *dev = dcr->dev;
 
-   Pmsg0(-1, _("\n=== Write, rewind, and re-read test ===\n\n"
-      "I'm going to write 1000 records and an EOF\n"
-      "then write 1000 records and an EOF, then rewind,\n"
+   /*
+    * Set big max_file_size so that write_record_to_block
+    * doesn't insert any additional EOF marks
+    */
+   dev->max_file_size = 2 * num_recs * dev->max_block_size;
+   Pmsg2(-1, _("\n=== Write, rewind, and re-read test ===\n\n"
+      "I'm going to write %d records and an EOF\n"
+      "then write %d records and an EOF, then rewind,\n"
       "and re-read the data to verify that it is correct.\n\n"
-      "This is an *essential* feature ...\n\n"));
+      "This is an *essential* feature ...\n\n"), num_recs, num_recs);
+
    block = dcr->block;
+   empty_block(block);
    rec = new_record();
+   rec->data = check_pool_memory_size(rec->data, block->buf_len);
+   rec->data_len = block->buf_len-100;
+   len = rec->data_len/sizeof(i);
+
    if (!dev->rewind(dcr)) {
       Pmsg1(0, _("Bad status from rewind. ERR=%s\n"), dev->bstrerror());
       goto bail_out;
    }
-   rec->data = check_pool_memory_size(rec->data, block->buf_len);
-   rec->data_len = block->buf_len-100;
-   len = rec->data_len/sizeof(i);
-   for (i=1; i<=1000; i++) {
+
+   for (i=1; i<=num_recs; i++) {
       p = (int *)rec->data;
       for (j=0; j<len; j++) {
          *p++ = i;
       }
-      if (!write_record_to_block(block, rec)) {
+      if (!write_record_to_block(dcr, rec)) {
          Pmsg0(0, _("Error writing record to block.\n"));
          goto bail_out;
       }
-      if (!write_block_to_dev(dcr)) {
+      if (!dcr->write_block_to_dev()) {
          Pmsg0(0, _("Error writing block to device.\n"));
          goto bail_out;
       }
    }
-   Pmsg1(0, _("Wrote 1000 blocks of %d bytes.\n"), rec->data_len);
+   Pmsg2(0, _("Wrote %d blocks of %d bytes.\n"), num_recs, rec->data_len);
    weofcmd();
-   for (i=1001; i<=2000; i++) {
+   for (i=num_recs+1; i<=2*num_recs; i++) {
       p = (int *)rec->data;
       for (j=0; j<len; j++) {
          *p++ = i;
       }
-      if (!write_record_to_block(block, rec)) {
+      if (!write_record_to_block(dcr, rec)) {
          Pmsg0(0, _("Error writing record to block.\n"));
          goto bail_out;
       }
-      if (!write_block_to_dev(dcr)) {
+      if (!dcr->write_block_to_dev()) {
          Pmsg0(0, _("Error writing block to device.\n"));
          goto bail_out;
       }
    }
-   Pmsg1(0, _("Wrote 1000 blocks of %d bytes.\n"), rec->data_len);
+   Pmsg2(0, _("Wrote %d blocks of %d bytes.\n"), num_recs, rec->data_len);
    weofcmd();
    if (dev->has_cap(CAP_TWOEOF)) {
       weofcmd();
    }
+   rc = true;
+
+bail_out:
+   free_record(rec);
+   if (!rc) {
+      exit_code = 1;
+   }
+   return rc;
+
+}
+
+/*
+ * This test writes Bacula blocks to the tape in
+ *   several files. It then rewinds the tape and attepts
+ *   to read these blocks back checking the data.
+ */
+static bool write_read_test()
+{
+   DEV_BLOCK *block;
+   DEV_RECORD *rec;
+   bool rc = false;
+   int len, i, j;
+   int *p;
+
+   rec = new_record();
+
+   if (!write_two_files()) {
+      goto bail_out;
+   }
+
+   block = dcr->block;
+   empty_block(block);
+
    if (!dev->rewind(dcr)) {
       Pmsg1(0, _("Bad status from rewind. ERR=%s\n"), dev->bstrerror());
       goto bail_out;
    } else {
       Pmsg0(0, _("Rewind OK.\n"));
    }
-   for (i=1; i<=2000; i++) {
+
+   rec->data = check_pool_memory_size(rec->data, block->buf_len);
+   rec->data_len = block->buf_len-100;
+   len = rec->data_len/sizeof(i);
+
+   /* Now read it back */
+   for (i=1; i<=2*num_recs; i++) {
 read_again:
-      if (!read_block_from_dev(dcr, NO_BLOCK_NUMBER_CHECK)) {
+      if (!dcr->read_block_from_dev(NO_BLOCK_NUMBER_CHECK)) {
          berrno be;
          if (dev_state(dev, ST_EOF)) {
             Pmsg0(-1, _("Got EOF on tape.\n"));
-            if (i == 1001) {
+            if (i == num_recs+1) {
                goto read_again;
             }
          }
@@ -869,7 +1234,7 @@ read_again:
          goto bail_out;
       }
       memset(rec->data, 0, rec->data_len);
-      if (!read_record_from_block(dcr, block, rec)) {
+      if (!read_record_from_block(dcr, rec)) {
          berrno be;
          Pmsg2(0, _("Read record failed. Block %d! ERR=%s\n"), i, be.bstrerror(dev->dev_errno));
          goto bail_out;
@@ -883,16 +1248,19 @@ read_again:
          }
          p++;
       }
-      if (i == 1000 || i == 2000) {
-         Pmsg0(-1, _("1000 blocks re-read correctly.\n"));
+      if (i == num_recs || i == 2*num_recs) {
+         Pmsg1(-1, _("%d blocks re-read correctly.\n"), num_recs);
       }
    }
    Pmsg0(-1, _("=== Test Succeeded. End Write, rewind, and re-read test ===\n\n"));
-   stat = 1;
+   rc = true;
 
 bail_out:
    free_record(rec);
-   return stat;
+   if (!rc) {
+      exit_code = 1;   
+   }
+   return rc;
 }
 
 /*
@@ -900,67 +1268,26 @@ bail_out:
  *   several files. It then rewinds the tape and attepts
  *   to read these blocks back checking the data.
  */
-static int position_test()
+static bool position_test()
 {
    DEV_BLOCK *block = dcr->block;
    DEV_RECORD *rec;
-   int stat = 0;
-   int len, i, j;
-   bool ok = true;
+   bool rc = false;
+   int len, j;
+   bool more = true;
    int recno = 0;
    int file = 0, blk = 0;
    int *p;
    bool got_eof = false;
 
-   Pmsg0(-1, _("\n=== Write, rewind, and position test ===\n\n"
-      "I'm going to write 1000 records and an EOF\n"
-      "then write 1000 records and an EOF, then rewind,\n"
-      "and position to a few blocks and verify that it is correct.\n\n"
-      "This is an *essential* feature ...\n\n"));
+   Pmsg0(0, _("Block position test\n"));
+   block = dcr->block;
    empty_block(block);
    rec = new_record();
-   if (!dev->rewind(dcr)) {
-      Pmsg1(0, _("Bad status from rewind. ERR=%s\n"), dev->bstrerror());
-      goto bail_out;
-   }
    rec->data = check_pool_memory_size(rec->data, block->buf_len);
    rec->data_len = block->buf_len-100;
-   len = rec->data_len/sizeof(i);
-   for (i=1; i<=1000; i++) {
-      p = (int *)rec->data;
-      for (j=0; j<len; j++) {
-         *p++ = i;
-      }
-      if (!write_record_to_block(block, rec)) {
-         Pmsg0(0, _("Error writing record to block.\n"));
-         goto bail_out;
-      }
-      if (!write_block_to_dev(dcr)) {
-         Pmsg0(0, _("Error writing block to device.\n"));
-         goto bail_out;
-      }
-   }
-   Pmsg1(0, _("Wrote 1000 blocks of %d bytes.\n"), rec->data_len);
-   weofcmd();
-   for (i=1001; i<=2000; i++) {
-      p = (int *)rec->data;
-      for (j=0; j<len; j++) {
-         *p++ = i;
-      }
-      if (!write_record_to_block(block, rec)) {
-         Pmsg0(0, _("Error writing record to block.\n"));
-         goto bail_out;
-      }
-      if (!write_block_to_dev(dcr)) {
-         Pmsg0(0, _("Error writing block to device.\n"));
-         goto bail_out;
-      }
-   }
-   Pmsg1(0, _("Wrote 1000 blocks of %d bytes.\n"), rec->data_len);
-   weofcmd();
-   if (dev->has_cap(CAP_TWOEOF)) {
-      weofcmd();
-   }
+   len = rec->data_len/sizeof(j);
+
    if (!dev->rewind(dcr)) {
       Pmsg1(0, _("Bad status from rewind. ERR=%s\n"), dev->bstrerror());
       goto bail_out;
@@ -968,8 +1295,11 @@ static int position_test()
       Pmsg0(0, _("Rewind OK.\n"));
    }
 
-   while(ok) {
+   while (more) {
       /* Set up next item to read based on where we are */
+      /* At each step, recno is what we print for the "block number"
+       *  and file, blk are the real positions to go to.
+       */
       switch (recno) {
       case 0:
          recno = 5;
@@ -982,27 +1312,27 @@ static int position_test()
          blk = 200;
          break;
       case 201:
-         recno = 1000;
+         recno = num_recs;
          file = 0;
-         blk = 999;
+         blk = num_recs-1;
          break;
-      case 1000:
-         recno = 1001;
+      case num_recs:
+         recno = num_recs+1;
          file = 1;
          blk = 0;
          break;
-      case 1001:
-         recno = 1601;
+      case num_recs+1:
+         recno = num_recs+601;
          file = 1;
          blk = 600;
          break;
-      case 1601:
-         recno = 2000;
+      case num_recs+601:
+         recno = 2*num_recs;
          file = 1;
-         blk = 999;
+         blk = num_recs-1;
          break;
-      case 2000:
-         ok = false;
+      case 2*num_recs:
+         more = false;
          continue;
       }
       Pmsg2(-1, _("Reposition to file:block %d:%d\n"), file, blk);
@@ -1011,7 +1341,7 @@ static int position_test()
          goto bail_out;
       }
 read_again:
-      if (!read_block_from_dev(dcr, NO_BLOCK_NUMBER_CHECK)) {
+      if (!dcr->read_block_from_dev(NO_BLOCK_NUMBER_CHECK)) {
          berrno be;
          if (dev_state(dev, ST_EOF)) {
             Pmsg0(-1, _("Got EOF on tape.\n"));
@@ -1036,7 +1366,7 @@ read_again:
          goto bail_out;
       }
       memset(rec->data, 0, rec->data_len);
-      if (!read_record_from_block(dcr, block, rec)) {
+      if (!read_record_from_block(dcr, rec)) {
          berrno be;
          Pmsg1(0, _("Read record failed! ERR=%s\n"), be.bstrerror(dev->dev_errno));
          goto bail_out;
@@ -1052,11 +1382,11 @@ read_again:
       Pmsg1(-1, _("Block %d re-read correctly.\n"), recno);
    }
    Pmsg0(-1, _("=== Test Succeeded. End Write, rewind, and re-read test ===\n\n"));
-   stat = 1;
+   rc = true;
 
 bail_out:
    free_record(rec);
-   return stat;
+   return rc;
 }
 
 
@@ -1279,7 +1609,7 @@ static void autochangercmd()
  * This test assumes that the append test has been done,
  *   then it tests the fsf function.
  */
-static int fsf_test()
+static bool fsf_test()
 {
    bool set_off = false;
 
@@ -1361,7 +1691,7 @@ test_again:
       goto bail_out;
    }
    Pmsg0(-1, _("\n=== End Forward space files test ===\n\n"));
-   return 1;
+   return true;
 
 bail_out:
    Pmsg0(-1, _("\nThe forward space file test failed.\n"));
@@ -1376,7 +1706,7 @@ bail_out:
             "Some systems, e.g. OpenBSD, require you to set\n"
             "   Use MTIOCGET= no\n"
             "in your device resource. Use with caution.\n"));
-   return -2;
+   return false;
 }
 
 
@@ -1392,9 +1722,11 @@ static void testcmd()
    int stat;
 
    if (!write_read_test()) {
+      exit_code = 1;
       return;
    }
    if (!position_test()) {
+      exit_code = 1;
       return;
    }
 
@@ -1457,6 +1789,7 @@ failed:
             "Some systems, e.g. OpenBSD, require you to set\n"
             "   Use MTIOCGET= no\n"
             "in your device resource. Use with caution.\n"));
+       exit_code = 1;
        return;
    }
 
@@ -1480,10 +1813,14 @@ all_done:
         "the tape.\n\n"));
 
    if (stat == 1) {
-      re_read_block_test();
+      if (!re_read_block_test()) {
+         exit_code = 1;
+      }
    }
 
-   fsf_test();                        /* do fast forward space file test */
+   if (!fsf_test()) {                  /* do fast forward space file test */
+      exit_code = 1;
+   }
 
    autochanger_test();                /* do autochanger test */
 
@@ -1539,7 +1876,7 @@ static void fsrcmd()
 static void rbcmd()
 {
    dev->open(dcr, OPEN_READ_ONLY);
-   read_block_from_dev(dcr, NO_BLOCK_NUMBER_CHECK);  
+   dcr->read_block_from_dev(NO_BLOCK_NUMBER_CHECK);
 }
 
 /*
@@ -1554,7 +1891,7 @@ static void wrcmd()
    if (!dev->is_open()) {
       open_the_device();
    }
-   sm_check(__FILE__, __LINE__, false);
+   Dsm_check(200);
    empty_block(block);
    if (verbose > 1) {
       dump_block(block, "test");
@@ -1565,12 +1902,12 @@ static void wrcmd()
    rec->data = check_pool_memory_size(rec->data, i);
    memset(rec->data, i & 0xFF, i);
    rec->data_len = i;
-   sm_check(__FILE__, __LINE__, false);
-   if (!write_record_to_block(block, rec)) {
+   Dsm_check(200);
+   if (!write_record_to_block(dcr, rec)) {
       Pmsg0(0, _("Error writing record to block.\n"));
       goto bail_out;
    }
-   if (!write_block_to_dev(dcr)) {
+   if (!dcr->write_block_to_dev()) {
       Pmsg0(0, _("Error writing block to device.\n"));
       goto bail_out;
    } else {
@@ -1579,8 +1916,7 @@ static void wrcmd()
    Pmsg0(0, _("Wrote block to device.\n"));
 
 bail_out:
-   sm_check(__FILE__, __LINE__, false);
-   sm_check(__FILE__, __LINE__, false);
+   Dsm_check(200);
 }
 
 /*
@@ -1716,7 +2052,7 @@ static void scan_blocks()
    dev->update_pos(dcr);
    tot_files = dev->file;
    for (;;) {
-      if (!read_block_from_device(dcr, NO_BLOCK_NUMBER_CHECK)) {
+      if (!dcr->read_block_from_device(NO_BLOCK_NUMBER_CHECK)) {
          Dmsg1(100, "!read_block(): ERR=%s\n", dev->bstrerror());
          if (dev->state & ST_EOT) {
             if (blocks > 0) {
@@ -1774,14 +2110,14 @@ static void scan_blocks()
       blocks++;
       tot_blocks++;
       bytes += block->block_len;
-      Dmsg6(100, "Blk_blk=%u dev_blk=%u blen=%u bVer=%d SessId=%u SessTim=%u\n",
-         block->BlockNumber, dev->block_num, block->block_len, block->BlockVer,
+      Dmsg7(100, "Blk_blk=%u file,blk=%u,%u blen=%u bVer=%d SessId=%u SessTim=%u\n",
+         block->BlockNumber, dev->file, dev->block_num, block->block_len, block->BlockVer,
          block->VolSessionId, block->VolSessionTime);
       if (verbose == 1) {
          DEV_RECORD *rec = new_record();
-         read_record_from_block(dcr, block, rec);
-         Pmsg8(-1, _("Blk_block: %u dev_blk=%u blen=%u First rec FI=%s SessId=%u SessTim=%u Strm=%s rlen=%d\n"),
-              block->BlockNumber, dev->block_num, block->block_len,
+         read_record_from_block(dcr, rec);
+         Pmsg9(-1, _("Block=%u file,blk=%u,%u blen=%u First rec FI=%s SessId=%u SessTim=%u Strm=%s rlen=%d\n"),
+              block->BlockNumber, dev->file, dev->block_num, block->block_len,
               FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, rec->VolSessionTime,
               stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len);
          rec->remainder = 0;
@@ -1818,11 +2154,12 @@ static void fillcmd()
 {
    DEV_RECORD rec;
    DEV_BLOCK  *block = dcr->block;
-   char ec1[50];
+   char ec1[50], ec2[50];
    char buf1[100], buf2[100];
-   int fd;
-   uint32_t i;
+   uint64_t write_eof;
+   uint64_t rate;
    uint32_t min_block_size;
+   int fd;
    struct tm tm;
 
    ok = true;
@@ -1831,8 +2168,9 @@ static void fillcmd()
    last_file = 0;
    last_block_num = 0;
    BlockNumber = 0;
+   exit_code = 0;
 
-   Pmsg0(-1, _("\n"
+   Pmsg1(-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.\n\n"
@@ -1840,14 +2178,15 @@ static void fillcmd()
 "the tapes that are in slots 1 and 2, otherwise, you will\n"
 "be prompted to insert the tapes when necessary.\n\n"
 "It will print a status approximately\n"
-"every 322 MB, and write an EOF every 3.2 GB.  If you have\n"
+"every 322 MB, and write an EOF every %s.  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\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 more \n"
 "blocks, it will stop.  Then it will begin re-reading the\n"
 "two tapes.\n\n"
-"This may take a long time -- hours! ...\n\n"));
+"This may take a long time -- hours! ...\n\n"),
+         edit_uint64_with_suffix(dev->max_file_size, buf1));
 
    get_cmd(_("Do you want to run the simplified test (s) with one tape\n"
            "or the complete multiple tape (m) test: (s/m) "));
@@ -1859,6 +2198,7 @@ static void fillcmd()
       simple = false;
    } else {
       Pmsg0(000, _("Command aborted.\n"));
+      exit_code = 1;
       return;
    }
 
@@ -1868,6 +2208,7 @@ static void fillcmd()
    /* Use fixed block size to simplify read back */
    min_block_size = dev->min_block_size;
    dev->min_block_size = dev->max_block_size;
+   write_eof = dev->max_file_size / REC_SIZE; /*compute when we add EOF*/
    set_volume_name("TestVolume1", 1);
    dir_ask_sysop_to_create_appendable_volume(dcr);
    dev->set_append();                 /* force volume to be relabeled */
@@ -1879,7 +2220,8 @@ static void fillcmd()
     */
    Dmsg0(100, "just before acquire_device\n");
    if (!acquire_device_for_append(dcr)) {
-      set_jcr_job_status(jcr, JS_ErrorTerminated);
+      jcr->setJobStatus(JS_ErrorTerminated);
+      exit_code = 1;
       return;
    }
    block = jcr->dcr->block;
@@ -1889,7 +2231,7 @@ static void fillcmd()
     * Write Begin Session Record
     */
    if (!write_session_label(dcr, SOS_LABEL)) {
-      set_jcr_job_status(jcr, JS_ErrorTerminated);
+      jcr->setJobStatus(JS_ErrorTerminated);
       Jmsg1(jcr, M_FATAL, 0, _("Write session label failed. ERR=%s\n"),
          dev->bstrerror());
       ok = false;
@@ -1898,24 +2240,12 @@ static void fillcmd()
 
    memset(&rec, 0, sizeof(rec));
    rec.data = get_memory(100000);     /* max record size */
-
-#define REC_SIZE 32768
    rec.data_len = REC_SIZE;
 
    /*
     * Put some random data in the record
     */
-   fd = open("/dev/urandom", O_RDONLY);
-   if (fd != -1) {
-      read(fd, rec.data, rec.data_len);
-      close(fd);
-   } else {
-      uint32_t *p = (uint32_t *)rec.data;
-      srandom(time(NULL));
-      for (i=0; i<rec.data_len/sizeof(uint32_t); i++) {
-         p[i] = random();
-      }
-   }
+   fill_buffer(FILL_RANDOM, rec.data, rec.data_len);
 
    /*
     * Generate data as if from File daemon, write to device
@@ -1934,20 +2264,17 @@ static void fillcmd()
       rec.VolSessionTime = jcr->VolSessionTime;
       rec.FileIndex = ++file_index;
       rec.Stream = STREAM_FILE_DATA;
+      rec.maskedStream = STREAM_FILE_DATA;
 
       /* Mix up the data just a bit */
-      uint32_t *lp = (uint32_t *)rec.data;
-      lp[0] += lp[13];
-      for (i=1; i < (rec.data_len-sizeof(uint32_t))/sizeof(uint32_t)-1; i++) {
-         lp[i] += lp[i-1];
-      }
+      mix_buffer(FILL_RANDOM, rec.data, rec.data_len);
 
       Dmsg4(250, "before write_rec FI=%d SessId=%d Strm=%s len=%d\n",
          rec.FileIndex, rec.VolSessionId, 
          stream_to_ascii(buf1, rec.Stream, rec.FileIndex),
          rec.data_len);
 
-      while (!write_record_to_block(block, &rec)) {
+      while (!write_record_to_block(dcr, &rec)) {
          /*
           * When we get here we have just filled a block
           */
@@ -1956,6 +2283,8 @@ static void fillcmd()
 
          /* Write block to tape */
          if (!flush_block(block, 1)) {
+            Pmsg0(000, _("Flush block failed.\n"));
+            exit_code = 1;
             break;
          }
 
@@ -1967,29 +2296,34 @@ static void fillcmd()
             if (now <= 0) {
                now = 1;          /* prevent divide error */
             }
-            kbs = (double)dev->VolCatInfo.VolCatBytes / (1000.0 * (double)now);
-            Pmsg4(-1, _("Wrote blk_block=%u, dev_blk_num=%u VolBytes=%s rate=%.1f KB/s\n"),
-               block->BlockNumber, dev->block_num,
-               edit_uint64_with_commas(dev->VolCatInfo.VolCatBytes, ec1), (float)kbs);
+            rate = dev->VolCatInfo.VolCatBytes / now;
+            Pmsg5(-1, _("Wrote block=%u, file,blk=%u,%u VolBytes=%s rate=%sB/s\n"),
+               block->BlockNumber, dev->file, dev->block_num,
+               edit_uint64_with_commas(dev->VolCatInfo.VolCatBytes, ec1),
+               edit_uint64_with_suffix(rate, ec2));
          }
-         /* Every 32000 blocks (approx 2GB) write an EOF.
+         /* Every X blocks (dev->max_file_size) write an EOF.
           */
-         if ((block->BlockNumber % 32000) == 0) {
+         if ((block->BlockNumber % write_eof) == 0) {
             now = time(NULL);
             (void)localtime_r(&now, &tm);
             strftime(buf1, sizeof(buf1), "%H:%M:%S", &tm);
             Pmsg1(-1, _("%s Flush block, write EOF\n"), buf1);
             flush_block(block, 0);
+#ifdef needed_xxx
             dev->weof(1);
+#endif
          }
 
-         /* Get out after writing 10 blocks to the second tape */
-         if (++BlockNumber > 10 && stop != 0) {      /* get out */
+         /* Get out after writing 1000 blocks to the second tape */
+         if (++BlockNumber > 1000 && stop != 0) {      /* get out */
+            Pmsg0(000, _("Wrote 1000 blocks on second tape. Done.\n"));
             break;
          }
       }
       if (!ok) {
          Pmsg0(000, _("Not OK\n"));
+         exit_code = 1;
          break;
       }
       jcr->JobBytes += rec.data_len;   /* increment bytes this job */
@@ -1997,30 +2331,35 @@ static void fillcmd()
          FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId,
          stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len);
 
-      /* Get out after writing 10 blocks to the second tape */
-      if (BlockNumber > 10 && stop != 0) {      /* get out */
+      /* Get out after writing 1000 blocks to the second tape */
+      if (BlockNumber > 1000 && stop != 0) {      /* get out */
          char ed1[50];
          Pmsg1(-1, "Done writing %s records ...\n", 
              edit_uint64_with_commas(write_count, ed1));
          break;
       }
-   }
+   } /* end big for loop */
+
    if (vol_num > 1) {
       Dmsg0(100, "Write_end_session_label()\n");
       /* Create Job status for end of session label */
       if (!job_canceled(jcr) && ok) {
-         set_jcr_job_status(jcr, JS_Terminated);
+         jcr->setJobStatus(JS_Terminated);
       } else if (!ok) {
-         set_jcr_job_status(jcr, JS_ErrorTerminated);
+         Pmsg0(000, _("Job canceled.\n"));
+         jcr->setJobStatus(JS_ErrorTerminated);
+         exit_code = 1;
       }
       if (!write_session_label(dcr, EOS_LABEL)) {
-         Pmsg1(000, _("Error writting end session label. ERR=%s\n"), dev->bstrerror());
+         Pmsg1(000, _("Error writing end session label. ERR=%s\n"), dev->bstrerror());
          ok = false;
+         exit_code = 1;
       }
       /* Write out final block of this session */
-      if (!write_block_to_device(dcr)) {
+      if (!dcr->write_block_to_device()) {
          Pmsg0(-1, _("Set ok=false after write_block_to_device.\n"));
          ok = false;
+         exit_code = 1;
       }
       Pmsg0(-1, _("Wrote End of Session label.\n"));
 
@@ -2046,29 +2385,37 @@ static void fillcmd()
       write(fd, last_block2->buf, last_block2->buf_len);
       write(fd, first_block->buf, first_block->buf_len);
       close(fd);
-      Pmsg2(-1, _("Wrote state file last_block_num1=%d last_block_num2=%d\n"),
+      Pmsg2(0, _("Wrote state file last_block_num1=%d last_block_num2=%d\n"),
          last_block_num1, last_block_num2);
    } else {
       berrno be;
-      Pmsg2(-1, _("Could not create state file: %s ERR=%s\n"), buf,
+      Pmsg2(0, _("Could not create state file: %s ERR=%s\n"), buf,
                  be.bstrerror());
+      exit_code = 1;
+      ok = false;
    }
 
    now = time(NULL);
    (void)localtime_r(&now, &tm);
    strftime(buf1, sizeof(buf1), "%H:%M:%S", &tm);
-   if (simple) {
-      Pmsg3(-1, _("\n\n%s Done filling tape at %d:%d. Now beginning re-read of tape ...\n"),
-         buf1, jcr->dcr->dev->file, jcr->dcr->dev->block_num);
-   }
-   else {
-      Pmsg3(-1, _("\n\n%s Done filling tapes at %d:%d. Now beginning re-read of first tape ...\n"),
-         buf1, jcr->dcr->dev->file, jcr->dcr->dev->block_num);
-   }
-
-   jcr->dcr->block = block;
-   do_unfill();
+   if (ok) {
+      if (simple) {
+         Pmsg3(0, _("\n\n%s Done filling tape at %d:%d. Now beginning re-read of tape ...\n"),
+               buf1, jcr->dcr->dev->file, jcr->dcr->dev->block_num);
+      } else {
+         Pmsg3(0, _("\n\n%s Done filling tapes at %d:%d. Now beginning re-read of first tape ...\n"),
+               buf1, jcr->dcr->dev->file, jcr->dcr->dev->block_num);
+      }
 
+      jcr->dcr->block = block;
+      if (!do_unfill()) {
+         Pmsg0(000, _("do_unfill failed.\n"));
+         exit_code = 1;
+         ok = false;
+      }
+   } else {
+      Pmsg1(000, _("%s: Error during test.\n"), buf1);
+   }
    dev->min_block_size = min_block_size;
    free_memory(rec.data);
 }
@@ -2083,6 +2430,7 @@ static void unfillcmd()
 {
    int fd;
 
+   exit_code = 0;
    last_block1 = new_block(dev);
    last_block2 = new_block(dev);
    first_block = new_block(dev);
@@ -2103,28 +2451,38 @@ static void unfillcmd()
       if (state_level != btape_state_level) {
           Pmsg0(-1, _("\nThe state file level has changed. You must redo\n"
                   "the fill command.\n"));
+          exit_code = 1;
           return;
        }
    } else {
       berrno be;
       Pmsg2(-1, _("\nCould not find the state file: %s ERR=%s\n"
              "You must redo the fill command.\n"), buf, be.bstrerror());
+      exit_code = 1;
       return;
    }
-   do_unfill();
+   if (!do_unfill()) {
+      exit_code = 1;
+   }
    this_block = NULL;
 }
 
-static void do_unfill()
+/*
+ * This is the second part of the fill command. After the tape or
+ *  tapes are written, we are called here to reread parts, particularly
+ *  the last block.
+ */
+static bool do_unfill()
 {
    DEV_BLOCK *block = dcr->block;
    int autochanger;
+   bool rc = false;
 
    dumped = 0;
    VolBytes = 0;
    LastBlock = 0;
 
-   Dmsg0(20, "Enter do_unfill\n");
+   Pmsg0(000, "Enter do_unfill\n");
    dev->set_cap(CAP_ANONVOLS);        /* allow reading any volume */
    dev->clear_cap(CAP_LABEL);         /* don't label anything here */
 
@@ -2162,8 +2520,10 @@ static void do_unfill()
       }
       autochanger = autoload_device(dcr, 1, NULL);
       if (autochanger != 1) {
+         Pmsg1(100, "Autochanger returned: %d\n", autochanger);
          dev->close();
          get_cmd(_("Mount first tape. Press enter when ready: "));
+         Pmsg0(000, "\n");
       }
    }
 
@@ -2194,13 +2554,14 @@ static void do_unfill()
       goto bail_out;
    }
    Pmsg1(-1, _("Reading block %u.\n"), last_block_num);
-   if (!read_block_from_device(dcr, NO_BLOCK_NUMBER_CHECK)) {
+   if (!dcr->read_block_from_device(NO_BLOCK_NUMBER_CHECK)) {
       Pmsg1(-1, _("Error reading block: ERR=%s\n"), dev->bstrerror());
       goto bail_out;
    }
    if (compare_blocks(last_block, block)) {
       if (simple) {
          Pmsg0(-1, _("\nThe last block on the tape matches. Test succeeded.\n\n"));
+         rc = true;
       } else {
          Pmsg0(-1, _("\nThe last block of the first tape matches.\n\n"));
       }
@@ -2224,8 +2585,10 @@ static void do_unfill()
 
    autochanger = autoload_device(dcr, 1, NULL);
    if (autochanger != 1) {
+      Pmsg1(100, "Autochanger returned: %d\n", autochanger);
       dev->close();
       get_cmd(_("Mount second tape. Press enter when ready: "));
+      Pmsg0(000, "\n");
    }
 
    dev->clear_read();
@@ -2243,7 +2606,7 @@ static void do_unfill()
       goto bail_out;
    }
    Pmsg1(-1, _("Reading block %d.\n"), dev->block_num);
-   if (!read_block_from_device(dcr, NO_BLOCK_NUMBER_CHECK)) {
+   if (!dcr->read_block_from_device(NO_BLOCK_NUMBER_CHECK)) {
       Pmsg1(-1, _("Error reading block: ERR=%s\n"), dev->bstrerror());
       goto bail_out;
    }
@@ -2259,18 +2622,20 @@ static void do_unfill()
       goto bail_out;
    }
    Pmsg1(-1, _("Reading block %d.\n"), dev->block_num);
-   if (!read_block_from_device(dcr, NO_BLOCK_NUMBER_CHECK)) {
+   if (!dcr->read_block_from_device(NO_BLOCK_NUMBER_CHECK)) {
       Pmsg1(-1, _("Error reading block: ERR=%s\n"), dev->bstrerror());
       goto bail_out;
    }
    if (compare_blocks(last_block, block)) {
       Pmsg0(-1, _("\nThe last block on the second tape matches. Test succeeded.\n\n"));
+      rc = true;
    }
 
 bail_out:
    free_block(last_block1);
    free_block(last_block2);
    free_block(first_block);
+   return rc;
 }
 
 /* Read 10000 records then stop */
@@ -2287,7 +2652,10 @@ static bool quickie_cb(DCR *dcr, DEV_RECORD *rec)
 static bool compare_blocks(DEV_BLOCK *last_block, DEV_BLOCK *block)
 {
    char *p, *q;
-   uint32_t CheckSum, block_len;
+   union {
+      uint32_t CheckSum;
+      uint32_t block_len;
+   };
    ser_declare;
 
    p = last_block->buf;
@@ -2319,10 +2687,6 @@ static bool compare_blocks(DEV_BLOCK *last_block, DEV_BLOCK *block)
    return true;
 }
 
-
-
-
-
 /*
  * Write current block to tape regardless of whether or
  *   not it is full. If the tape fills, attempt to
@@ -2330,7 +2694,8 @@ static bool compare_blocks(DEV_BLOCK *last_block, DEV_BLOCK *block)
  */
 static int flush_block(DEV_BLOCK *block, int dump)
 {
-   char ec1[50];
+   char ec1[50], ec2[50];
+   uint64_t rate;
    DEV_BLOCK *tblock;
    uint32_t this_file, this_block_num;
 
@@ -2344,7 +2709,7 @@ static int flush_block(DEV_BLOCK *block, int dump)
    /* Copy block */
    this_file = dev->file;
    this_block_num = dev->block_num;
-   if (!write_block_to_dev(dcr)) {
+   if (!dcr->write_block_to_dev()) {
       Pmsg3(000, _("Last block at: %u:%u this_dev_block_num=%d\n"),
                   last_file, last_block_num, this_block_num);
       if (vol_num == 1) {
@@ -2376,11 +2741,12 @@ static int flush_block(DEV_BLOCK *block, int dump)
       if (now <= 0) {
          now = 1;                     /* don't divide by zero */
       }
-      kbs = (double)dev->VolCatInfo.VolCatBytes / (1000 * now);
+      rate = dev->VolCatInfo.VolCatBytes / now;
       vol_size = dev->VolCatInfo.VolCatBytes;
-      Pmsg4(000, _("End of tape %d:%d. VolumeCapacity=%s. Write rate = %.1f KB/s\n"),
+      Pmsg4(000, _("End of tape %d:%d. Volume Bytes=%s. Write rate = %sB/s\n"),
          dev->file, dev->block_num,
-         edit_uint64_with_commas(dev->VolCatInfo.VolCatBytes, ec1), kbs);
+         edit_uint64_with_commas(dev->VolCatInfo.VolCatBytes, ec1), 
+         edit_uint64_with_suffix(rate, ec2));
 
       if (simple) {
          stop = -1;                   /* stop, but do simplified test */
@@ -2437,7 +2803,7 @@ static void qfillcmd()
       count = 1000;
    }
 
-   sm_check(__FILE__, __LINE__, false);
+   Dsm_check(200);
 
    i = block->buf_len - 100;
    ASSERT (i > 0);
@@ -2445,22 +2811,25 @@ static void qfillcmd()
    memset(rec->data, i & 0xFF, i);
    rec->data_len = i;
    rewindcmd();
+   init_speed();
+
    Pmsg1(0, _("Begin writing %d Bacula blocks to tape ...\n"), count);
    for (i=0; i < count; i++) {
       if (i % 100 == 0) {
          printf("+");
          fflush(stdout);
       }
-      if (!write_record_to_block(block, rec)) {
+      if (!write_record_to_block(dcr, rec)) {
          Pmsg0(0, _("Error writing record to block.\n"));
          goto bail_out;
       }
-      if (!write_block_to_dev(dcr)) {
+      if (!dcr->write_block_to_dev()) {
          Pmsg0(0, _("Error writing block to device.\n"));
          goto bail_out;
       }
    }
    printf("\n");
+   print_speed(dev->VolCatInfo.VolCatBytes);
    weofcmd();
    if (dev->has_cap(CAP_TWOEOF)) {
       weofcmd();
@@ -2469,7 +2838,7 @@ static void qfillcmd()
    scan_blocks();
 
 bail_out:
-   sm_check(__FILE__, __LINE__, false);
+   Dsm_check(200);
 }
 
 /*
@@ -2479,41 +2848,27 @@ static void rawfill_cmd()
 {
    DEV_BLOCK *block = dcr->block;
    int stat;
-   int fd;
    uint32_t block_num = 0;
    uint32_t *p;
    int my_errno;
-   uint32_t i;
 
-   fd = open("/dev/urandom", O_RDONLY);
-   if (fd) {
-      read(fd, block->buf, block->buf_len);
-      close(fd);
-   } else {
-      uint32_t *p = (uint32_t *)block->buf;
-      srandom(time(NULL));
-      for (i=0; i<block->buf_len/sizeof(uint32_t); i++) {
-         p[i] = random();
-      }
-   }
+   fill_buffer(FILL_RANDOM, block->buf, block->buf_len);
+   init_speed();
+
    p = (uint32_t *)block->buf;
    Pmsg1(0, _("Begin writing raw blocks of %u bytes.\n"), block->buf_len);
    for ( ;; ) {
       *p = block_num;
-      if (dev->is_tape()) {
-         stat = tape_write(dev->fd(), block->buf, block->buf_len);
-      } else {
-         stat = write(dev->fd(), block->buf, block->buf_len);
-      }
+      stat = dev->d_write(dev->fd(), block->buf, block->buf_len);
       if (stat == (int)block->buf_len) {
          if ((block_num++ % 100) == 0) {
             printf("+");
             fflush(stdout);
          }
-         p[0] += p[13];
-         for (i=1; i<(block->buf_len-sizeof(uint32_t))/sizeof(uint32_t)-1; i++) {
-            p[i] += p[i-1];
-         }
+
+         mix_buffer(FILL_RANDOM, block->buf, block->buf_len);
+
+         jcr->JobBytes += stat;
          continue;
       }
       break;
@@ -2523,6 +2878,8 @@ static void rawfill_cmd()
    berrno be;
    printf(_("Write failed at block %u. stat=%d ERR=%s\n"), block_num, stat,
       be.bstrerror(my_errno));
+   
+   print_speed(jcr->JobBytes);
    weofcmd();
 }
 
@@ -2551,6 +2908,7 @@ static struct cmdstruct commands[] = {
  {NT_("rewind"),    rewindcmd,    _("rewind the tape")},
  {NT_("scan"),      scancmd,      _("read() tape block by block to EOT and report")},
  {NT_("scanblocks"),scan_blocks,  _("Bacula read block by block to EOT and report")},
+ {NT_("speed"),     speed_test,   _("[file_size=n(GB)|nb_file=3|skip_zero|skip_random|skip_raw|skip_block] report drive speed")},
  {NT_("status"),    statcmd,      _("print tape status")},
  {NT_("test"),      testcmd,      _("General test Bacula tape functions")},
  {NT_("weof"),      weofcmd,      _("write an EOF on the tape")},
@@ -2568,7 +2926,7 @@ do_tape_cmds()
    bool found;
 
    while (!quit && get_cmd("*")) {
-      sm_check(__FILE__, __LINE__, false);
+      Dsm_check(200);
       found = false;
       parse_args(cmd, &args, &argc, argk, argv, MAX_CMD_ARGS);
       for (i=0; i<comsize; i++)       /* search for command */
@@ -2577,7 +2935,7 @@ do_tape_cmds()
             found = true;
             break;
          }
-      if (!found) {
+      if (*cmd && !found) {
          Pmsg1(0, _("\"%s\" is an invalid command\n"), cmd);
       }
    }
@@ -2622,7 +2980,8 @@ get_cmd(const char *prompt)
 {
    int i = 0;
    int ch;
-   fprintf(stdout, prompt);
+
+   fprintf(stdout, "%s", prompt);
 
    /* We really should turn off echoing and pretty this
     * up a bit.
@@ -2633,8 +2992,9 @@ get_cmd(const char *prompt)
          strip_trailing_junk(cmd);
          return 1;
       } else if (ch == 4 || ch == 0xd3 || ch == 0x8) {
-         if (i > 0)
+         if (i > 0) {
             cmd[--i] = 0;
+         }
          continue;
       }
 
@@ -2658,11 +3018,11 @@ bool dir_update_volume_info(DCR *dcr, bool relabel, bool update_LastWritten)
 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw  writing)
 {
    Dmsg0(20, "Enter dir_get_volume_info\n");
-   bstrncpy(dcr->VolCatInfo.VolCatName, dcr->VolumeName, sizeof(dcr->VolCatInfo.VolCatName));
+   dcr->setVolCatName(dcr->VolumeName);
    return 1;
 }
 
-bool dir_create_jobmedia_record(DCR *dcr)
+bool dir_create_jobmedia_record(DCR *dcr, bool zero)
 {
    dcr->WroteVol = false;
    return 1;
@@ -2675,7 +3035,7 @@ bool dir_find_next_appendable_volume(DCR *dcr)
    return dcr->VolumeName[0] != 0;
 }
 
-bool dir_ask_sysop_to_mount_volume(DCR *dcr)
+bool dir_ask_sysop_to_mount_volume(DCR *dcr, int /* mode */)
 {
    DEVICE *dev = dcr->dev;
    Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
@@ -2711,10 +3071,12 @@ bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr)
    }
    autochanger = autoload_device(dcr, 1, NULL);
    if (autochanger != 1) {
+      Pmsg1(100, "Autochanger returned: %d\n", autochanger);
       fprintf(stderr, _("Mount blank Volume on device %s and press return when ready: "),
          dev->print_name());
       dev->close();
       getchar();
+      Pmsg0(000, "\n");
    }
    labelcmd();
    VolumeName = NULL;
@@ -2724,7 +3086,8 @@ bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr)
 
 static bool my_mount_next_read_volume(DCR *dcr)
 {
-   char ec1[50];
+   char ec1[50], ec2[50];
+   uint64_t rate;
    JCR *jcr = dcr->jcr;
    DEV_BLOCK *block = dcr->block;
 
@@ -2732,6 +3095,7 @@ static bool my_mount_next_read_volume(DCR *dcr)
    Pmsg2(000, _("End of Volume \"%s\" %d records.\n"), dcr->VolumeName,
       quickie_count);
 
+   volume_unused(dcr);             /* release current volume */
    if (LastBlock != block->BlockNumber) {
       VolBytes += block->block_len;
    }
@@ -2741,9 +3105,10 @@ static bool my_mount_next_read_volume(DCR *dcr)
    if (now <= 0) {
       now = 1;
    }
-   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);
+   rate = VolBytes / now;
+   Pmsg3(-1, _("Read block=%u, VolBytes=%s rate=%sB/s\n"), block->BlockNumber,
+            edit_uint64_with_commas(VolBytes, ec1), 
+            edit_uint64_with_suffix(rate, ec2));
 
    if (strcmp(dcr->VolumeName, "TestVolume2") == 0) {
       end_of_tape = 1;
@@ -2765,8 +3130,8 @@ static void set_volume_name(const char *VolName, int volnum)
    DCR *dcr = jcr->dcr;
    VolumeName = VolName;
    vol_num = volnum;
-   bstrncpy(dev->VolCatInfo.VolCatName, VolName, sizeof(dev->VolCatInfo.VolCatName));
-   bstrncpy(dcr->VolCatInfo.VolCatName, VolName, sizeof(dcr->VolCatInfo.VolCatName));
+   dev->setVolCatName(VolName);
+   dcr->setVolCatName(VolName);
    bstrncpy(dcr->VolumeName, VolName, sizeof(dcr->VolumeName));
    dcr->VolCatInfo.Slot = volnum;
    dcr->VolCatInfo.InChanger = true;