]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/stored/btape.c
Backport more master code
[bacula/bacula] / bacula / src / stored / btape.c
index cf1f065730a1b8120994608c434400c19757712b..967887290d4de97d85527c210001ec3654b60246 100644 (file)
@@ -1,3 +1,30 @@
+/*
+   Bacula® - The Network Backup Solution
+
+   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 three of the GNU Affero General Public
+   License as published by the Free Software Foundation and included
+   in the file LICENSE.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   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 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 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.
+*/
 /*
  *
  *   Bacula Tape manipulation program
  *   Note, this program reads stored.conf, and will only
  *     talk to devices that are configured.
  *
- *   Version $Id$
- *
- */
-/*
-   Copyright (C) 2000-2006 Kern Sibbald
-
-   This program is free software; you can redistribute it and/or
-   modify it under the terms of the GNU General Public License
-   version 2 as amended with additional clauses defined in the
-   file LICENSE in the main source directory.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
-   the file LICENSE for additional details.
-
  */
 
 #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();
@@ -56,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();
@@ -79,13 +97,13 @@ static bool my_mount_next_read_volume(DCR *dcr);
 static void scan_blocks();
 static void set_volume_name(const char *VolName, int volnum);
 static void rawfill_cmd();
-static void bfill_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;
 
@@ -105,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;
@@ -154,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) {
@@ -163,9 +181,9 @@ int main(int margc, char *margv[])
    if (TAPE_BSIZE != (1 << (ffs(TAPE_BSIZE)-1))) {
       Emsg1(M_ABORT, 0, _("Tape block size (%d) is not a power of 2\n"), TAPE_BSIZE);
    }
-   if (sizeof(off_t) < 8) {
-      Pmsg1(-1, _("\n\n!!!! Warning large disk addressing disabled. off_t=%d should be 8 or more !!!!!\n\n\n"),
-         sizeof(off_t));
+   if (sizeof(boffset_t) < 8) {
+      Pmsg1(-1, _("\n\n!!!! Warning large disk addressing disabled. boffset_t=%d should be 8 or more !!!!!\n\n\n"),
+         sizeof(boffset_t));
    }
    x32 = 123456789;
    bsnprintf(buf, sizeof(buf), "%u", x32);
@@ -208,9 +226,13 @@ int main(int margc, char *margv[])
          break;
 
       case 'd':                    /* set debug level */
-         debug_level = atoi(optarg);
-         if (debug_level <= 0) {
-            debug_level = 1;
+         if (*optarg == 't') {
+            dbg_timestamp = true;
+         } else {
+            debug_level = atoi(optarg);
+            if (debug_level <= 0) {
+               debug_level = 1;
+            }
          }
          break;
 
@@ -249,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) {
@@ -283,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;
@@ -315,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();
 
@@ -333,31 +358,129 @@ 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;
+   bool ok = true;
 
    block = new_block(dev);
-   lock_device(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);
-      unlock_device(dev);
-      free_block(block);
-      return false;
+      ok = false;
+      goto bail_out;
    }
    Pmsg1(000, _("open device %s: OK\n"), dev->print_name());
    dev->set_append();                 /* put volume in append mode */
-   unlock_device(dev);
+
+bail_out:
+   dev->dunlock();
    free_block(block);
-   return true;
+   return ok;
 }
 
 
@@ -605,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"));
@@ -626,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);
 }
 
 /*
@@ -656,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"
@@ -680,47 +805,47 @@ 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 {
       Pmsg1(0, _("Wrote third record of %d bytes.\n"), rec->data_len);
    }
    weofcmd();
-   if (dev_cap(dev, CAP_TWOEOF)) {
+   if (dev->has_cap(CAP_TWOEOF)) {
       weofcmd();
    }
    if (!dev->bsf(1)) {
       Pmsg1(0, _("Backspace file failed! ERR=%s\n"), dev->bstrerror());
       goto bail_out;
    }
-   if (dev_cap(dev, CAP_TWOEOF)) {
+   if (dev->has_cap(CAP_TWOEOF)) {
       if (!dev->bsf(1)) {
          Pmsg1(0, _("Backspace file failed! ERR=%s\n"), dev->bstrerror());
          goto bail_out;
@@ -732,15 +857,15 @@ 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.strerror(dev->dev_errno));
+      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(block, rec)) {
+   if (!read_record_from_block(dcr, rec)) {
       berrno be;
-      Pmsg1(0, _("Read block failed! ERR=%s\n"), be.strerror(dev->dev_errno));
+      Pmsg1(0, _("Read block failed! ERR=%s\n"), be.bstrerror(dev->dev_errno));
       goto bail_out;
    }
    for (int i=0; i<len; i++) {
@@ -752,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"
@@ -764,95 +889,354 @@ 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_cap(dev, CAP_TWOEOF)) {
+   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;
             }
          }
-         Pmsg2(0, _("Read block %d failed! ERR=%s\n"), i, be.strerror(dev->dev_errno));
+         Pmsg2(0, _("Read block %d failed! ERR=%s\n"), i, be.bstrerror(dev->dev_errno));
          goto bail_out;
       }
       memset(rec->data, 0, rec->data_len);
-      if (!read_record_from_block(block, rec)) {
+      if (!read_record_from_block(dcr, rec)) {
          berrno be;
-         Pmsg2(0, _("Read record failed. Block %d! ERR=%s\n"), i, be.strerror(dev->dev_errno));
+         Pmsg2(0, _("Read record failed. Block %d! ERR=%s\n"), i, be.bstrerror(dev->dev_errno));
          goto bail_out;
       }
       p = (int *)rec->data;
@@ -864,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;
 }
 
 /*
@@ -881,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_cap(dev, 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;
@@ -949,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;
@@ -963,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);
@@ -992,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"));
@@ -1002,7 +1351,7 @@ read_again:
             }
          }
          Pmsg4(0, _("Read block %d failed! file=%d blk=%d. ERR=%s\n\n"),
-            recno, file, blk, be.strerror(dev->dev_errno));
+            recno, file, blk, be.bstrerror(dev->dev_errno));
          Pmsg0(0, _("This may be because the tape drive block size is not\n"
                     " set to variable blocking as normally used by Bacula.\n"
                     " Please see the Tape Testing chapter in the manual and \n"
@@ -1017,9 +1366,9 @@ read_again:
          goto bail_out;
       }
       memset(rec->data, 0, rec->data_len);
-      if (!read_record_from_block(block, rec)) {
+      if (!read_record_from_block(dcr, rec)) {
          berrno be;
-         Pmsg1(0, _("Read record failed! ERR=%s\n"), be.strerror(dev->dev_errno));
+         Pmsg1(0, _("Read record failed! ERR=%s\n"), be.bstrerror(dev->dev_errno));
          goto bail_out;
       }
       p = (int *)rec->data;
@@ -1033,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;
 }
 
 
@@ -1067,7 +1416,7 @@ static int append_test()
    wrcmd();
    wrcmd();
    weofcmd();     /* end file 2 */
-   if (dev_cap(dev, CAP_TWOEOF)) {
+   if (dev->has_cap(CAP_TWOEOF)) {
       weofcmd();
    }
    dev->close();              /* release device */
@@ -1087,7 +1436,7 @@ static int append_test()
    Pmsg0(-1, _("\nNow the important part, I am going to attempt to append to the tape.\n\n"));
    wrcmd();
    weofcmd();
-   if (dev_cap(dev, CAP_TWOEOF)) {
+   if (dev->has_cap(CAP_TWOEOF)) {
       weofcmd();
    }
    rewindcmd();
@@ -1116,7 +1465,7 @@ static int autochanger_test()
    int sleep_time = 0;
 
    Dmsg1(100, "Max changer wait = %d sec\n", timeout);
-   if (!dev_cap(dev, CAP_AUTOCHANGER)) {
+   if (!dev->has_cap(CAP_AUTOCHANGER)) {
       return 1;
    }
    if (!(dcr->device && dcr->device->changer_name && dcr->device->changer_command)) {
@@ -1153,7 +1502,7 @@ try_again:
    } else {
       berrno be;
       Pmsg1(-1, _("3991 Bad autochanger command: %s\n"), changer);
-      Pmsg2(-1, _("3991 result=\"%s\": ERR=%s\n"), results, be.strerror(status));
+      Pmsg2(-1, _("3991 result=\"%s\": ERR=%s\n"), results, be.bstrerror(status));
       goto bail_out;
    }
    if (loaded) {
@@ -1175,7 +1524,7 @@ try_again:
       if (status != 0) {
          berrno be;
          Pmsg1(-1, _("3992 Bad autochanger command: %s\n"), changer);
-         Pmsg2(-1, _("3992 result=\"%s\": ERR=%s\n"), results, be.strerror(status));
+         Pmsg2(-1, _("3992 result=\"%s\": ERR=%s\n"), results, be.bstrerror(status));
       }
    }
 
@@ -1198,7 +1547,7 @@ try_again:
    } else {
       berrno be;
       Pmsg1(-1, _("3993 Bad autochanger command: %s\n"), changer);
-      Pmsg2(-1, _("3993 result=\"%s\": ERR=%s\n"), results, be.strerror(status));
+      Pmsg2(-1, _("3993 result=\"%s\": ERR=%s\n"), results, be.bstrerror(status));
       goto bail_out;
    }
 
@@ -1260,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;
 
@@ -1283,7 +1632,7 @@ static int fsf_test()
    weofcmd();     /* end file 3 */
    wrcmd();
    weofcmd();     /* end file 4 */
-   if (dev_cap(dev, CAP_TWOEOF)) {
+   if (dev->has_cap(CAP_TWOEOF)) {
       weofcmd();
    }
 
@@ -1342,14 +1691,14 @@ 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"));
-   if (dev_cap(dev, CAP_FASTFSF)) {
+   if (dev->has_cap(CAP_FASTFSF)) {
       Pmsg0(-1, _("You have Fast Forward Space File enabled.\n"
               "I am turning it off then retrying the test.\n"));
-      dev->capabilities &= ~CAP_FASTFSF;
+      dev->clear_cap(CAP_FASTFSF);
       set_off = true;
       goto test_again;
    }
@@ -1357,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;
 }
 
 
@@ -1373,9 +1722,11 @@ static void testcmd()
    int stat;
 
    if (!write_read_test()) {
+      exit_code = 1;
       return;
    }
    if (!position_test()) {
+      exit_code = 1;
       return;
    }
 
@@ -1384,13 +1735,13 @@ static void testcmd()
       goto all_done;
    }
    if (stat == -1) {                  /* first test failed */
-      if (dev_cap(dev, CAP_EOM) || dev_cap(dev, CAP_FASTFSF)) {
+      if (dev->has_cap(CAP_EOM) || dev->has_cap(CAP_FASTFSF)) {
          Pmsg0(-1, _("\nAppend test failed. Attempting again.\n"
                    "Setting \"Hardware End of Medium = no\n"
                    "    and \"Fast Forward Space File = no\n"
                    "and retrying append test.\n\n"));
-         dev->capabilities &= ~CAP_EOM; /* turn off eom */
-         dev->capabilities &= ~CAP_FASTFSF; /* turn off fast fsf */
+         dev->clear_cap(CAP_EOM);      /* turn off eom */
+         dev->clear_cap(CAP_FASTFSF);  /* turn off fast fsf */
          stat = append_test();
          if (stat == 1) {
             Pmsg0(-1, _("\n\nIt looks like the test worked this time, please add:\n\n"
@@ -1438,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;
    }
 
@@ -1461,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 */
 
@@ -1520,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);
 }
 
 /*
@@ -1532,8 +1888,10 @@ static void wrcmd()
    DEV_RECORD *rec = dcr->rec;
    int i;
 
-   open_the_device();
-   sm_check(__FILE__, __LINE__, false);
+   if (!dev->is_open()) {
+      open_the_device();
+   }
+   Dsm_check(200);
    empty_block(block);
    if (verbose > 1) {
       dump_block(block, "test");
@@ -1544,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 {
@@ -1558,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);
 }
 
 /*
@@ -1579,13 +1936,13 @@ static void rrcmd()
       len = 1024;
    }
    buf = (char *)malloc(len);
-   stat = read(dev->fd, buf, len);
+   stat = read(dev->fd(), buf, len);
    if (stat > 0 && stat <= len) {
       errno = 0;
    }
    berrno be;
    Pmsg3(0, _("Read of %d bytes gives stat=%d. ERR=%s\n"),
-      len, stat, be.strerror());
+      len, stat, be.bstrerror());
    free(buf);
 }
 
@@ -1614,11 +1971,11 @@ static void scancmd()
    tot_files = dev->file;
    Pmsg1(0, _("Starting scan at file %u\n"), dev->file);
    for (;;) {
-      if ((stat = read(dev->fd, buf, sizeof(buf))) < 0) {
+      if ((stat = read(dev->fd(), buf, sizeof(buf))) < 0) {
          berrno be;
          dev->clrerror(-1);
          Mmsg2(dev->errmsg, _("read error on %s. ERR=%s.\n"),
-            dev->dev_name, be.strerror());
+            dev->dev_name, be.bstrerror());
          Pmsg2(0, _("Bad status from read %d. ERR=%s\n"), stat, dev->bstrerror());
          if (blocks > 0) {
             if (blocks==1) {
@@ -1695,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) {
@@ -1753,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(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;
@@ -1797,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;
@@ -1810,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"
@@ -1819,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) "));
@@ -1838,6 +2198,7 @@ static void fillcmd()
       simple = false;
    } else {
       Pmsg0(000, _("Command aborted.\n"));
+      exit_code = 1;
       return;
    }
 
@@ -1847,15 +2208,9 @@ 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);
-
-   if (!dev->rewind(dcr)) {
-      Pmsg0(000, _("Rewind failed.\n"));
-   }
-   if (!dev->weof(1)) {
-      Pmsg0(000, _("Write EOF failed.\n"));
-   }
-   labelcmd();
+   dir_ask_sysop_to_create_appendable_volume(dcr);
    dev->set_append();                 /* force volume to be relabeled */
 
    /*
@@ -1865,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;
@@ -1875,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;
@@ -1884,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) {
-      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
@@ -1920,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
           */
@@ -1942,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;
          }
 
@@ -1953,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 */
@@ -1983,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"));
 
@@ -2032,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,
-                 be.strerror());
+      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);
 }
@@ -2069,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);
@@ -2089,30 +2451,40 @@ 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.strerror());
+             "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;
-   bool autochanger;
+   int autochanger;
+   bool rc = false;
 
    dumped = 0;
    VolBytes = 0;
    LastBlock = 0;
 
-   Dmsg0(20, "Enter do_unfill\n");
-   dev->capabilities |= CAP_ANONVOLS; /* allow reading any volume */
-   dev->capabilities &= ~CAP_LABEL;   /* don't label anything here */
+   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 */
 
    end_of_tape = 0;
 
@@ -2127,24 +2499,34 @@ static void do_unfill()
    last_file = last_file1;
    last_block = last_block1;
 
+   free_restore_volume_list(jcr);
+   jcr->bsr = NULL;
+   bstrncpy(dcr->VolumeName, "TestVolume1|TestVolume2", sizeof(dcr->VolumeName));
+   create_restore_volume_list(jcr);
+   if (jcr->VolList != NULL) {
+      jcr->VolList->Slot = 1;
+      if (jcr->VolList->next != NULL) {
+         jcr->VolList->next->Slot = 2;
+      }
+   }
+
+   set_volume_name("TestVolume1", 1);
+
    if (!simple) {
       /* Multiple Volume tape */
       /* Close device so user can use autochanger if desired */
-      if (dev_cap(dev, CAP_OFFLINEUNMOUNT)) {
+      if (dev->has_cap(CAP_OFFLINEUNMOUNT)) {
          dev->offline();
       }
       autochanger = autoload_device(dcr, 1, NULL);
-      if (!autochanger) {
+      if (autochanger != 1) {
+         Pmsg1(100, "Autochanger returned: %d\n", autochanger);
          dev->close();
          get_cmd(_("Mount first tape. Press enter when ready: "));
+         Pmsg0(000, "\n");
       }
    }
 
-   free_restore_volume_list(jcr);
-   jcr->dcr = new_dcr(jcr, dev);
-   set_volume_name("TestVolume1", 1);
-   jcr->bsr = NULL;
-   create_restore_volume_list(jcr);
    dev->close();
    dev->num_writers = 0;
    if (!acquire_device_for_read(dcr)) {
@@ -2172,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"));
       }
@@ -2194,18 +2577,18 @@ static void do_unfill()
 
    /* Multiple Volume tape */
    /* Close device so user can use autochanger if desired */
-   if (dev_cap(dev, CAP_OFFLINEUNMOUNT)) {
+   if (dev->has_cap(CAP_OFFLINEUNMOUNT)) {
       dev->offline();
    }
 
-   free_restore_volume_list(jcr);
    set_volume_name("TestVolume2", 2);
-   jcr->bsr = NULL;
-   create_restore_volume_list(jcr);
+
    autochanger = autoload_device(dcr, 1, NULL);
-   if (!autochanger) {
+   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();
@@ -2223,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;
    }
@@ -2239,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 */
@@ -2267,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;
@@ -2299,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
@@ -2310,11 +2694,12 @@ 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;
 
-   lock_device(dev);
+   dev->r_dlock();
    if (!this_block) {
       this_block = new_block(dev);
    }
@@ -2324,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) {
@@ -2356,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 */
@@ -2369,12 +2755,12 @@ static int flush_block(DEV_BLOCK *block, int dump)
          if (!fixup_device_block_write_error(jcr->dcr)) {
             Pmsg1(000, _("Cannot fixup device error. %s\n"), dev->bstrerror());
             ok = false;
-            unlock_device(dev);
+            dev->dunlock();
             return 0;
          }
          BlockNumber = 0;             /* start counting for second tape */
       }
-      unlock_device(dev);
+      dev->dunlock();
       return 1;                       /* end of tape reached */
    }
 
@@ -2393,7 +2779,7 @@ static int flush_block(DEV_BLOCK *block, int dump)
    last_file = this_file;
    last_block_num = this_block_num;
 
-   unlock_device(dev);
+   dev->dunlock();
    return 1;
 }
 
@@ -2417,7 +2803,7 @@ static void qfillcmd()
       count = 1000;
    }
 
-   sm_check(__FILE__, __LINE__, false);
+   Dsm_check(200);
 
    i = block->buf_len - 100;
    ASSERT (i > 0);
@@ -2425,31 +2811,34 @@ 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_cap(dev, CAP_TWOEOF)) {
+   if (dev->has_cap(CAP_TWOEOF)) {
       weofcmd();
    }
    rewindcmd();
    scan_blocks();
 
 bail_out:
-   sm_check(__FILE__, __LINE__, false);
+   Dsm_check(200);
 }
 
 /*
@@ -2459,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;
@@ -2502,65 +2877,19 @@ static void rawfill_cmd()
    printf("\n");
    berrno be;
    printf(_("Write failed at block %u. stat=%d ERR=%s\n"), block_num, stat,
-      be.strerror(my_errno));
+      be.bstrerror(my_errno));
+   
+   print_speed(jcr->JobBytes);
    weofcmd();
 }
 
 
-/*
- * Fill a tape using Bacula block writes
- */
-static void bfill_cmd()
-{
-   DEV_BLOCK *block = dcr->block;
-   uint32_t block_num = 0;
-   uint32_t *p;
-   int my_errno;
-   int fd;
-   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();
-      }
-   }
-   p = (uint32_t *)block->buf;
-   Pmsg1(0, _("Begin writing Bacula blocks of %u bytes.\n"), block->buf_len);
-   for ( ;; ) {
-      *p = block_num;
-      block->binbuf = block->buf_len;
-      block->bufp = block->buf + block->binbuf;
-      if (!write_block_to_dev(dcr)) {
-         break;
-      }
-      if ((block_num++ % 100) == 0) {
-         printf("+");
-         fflush(stdout);
-      }
-      p[0] += p[13];
-      for (i=1; i<(block->buf_len/sizeof(uint32_t)-1); i++) {
-         p[i] += p[i-1];
-      }
-   }
-   my_errno = errno;
-   printf("\n");
-   printf(_("Write failed at block %u.\n"), block_num);
-   weofcmd();
-}
-
 
 struct cmdstruct { const char *key; void (*func)(); const char *help; };
 static struct cmdstruct commands[] = {
  {NT_("autochanger"),autochangercmd, _("test autochanger")},
  {NT_("bsf"),       bsfcmd,       _("backspace file")},
  {NT_("bsr"),       bsrcmd,       _("backspace record")},
- {NT_("bfill"),     bfill_cmd,    _("fill tape using Bacula writes")},
  {NT_("cap"),       capcmd,       _("list device capabilities")},
  {NT_("clear"),     clearcmd,     _("clear tape errors")},
  {NT_("eod"),       eodcmd,       _("go to end of Bacula data for append")},
@@ -2579,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")},
@@ -2596,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 */
@@ -2605,8 +2935,8 @@ do_tape_cmds()
             found = true;
             break;
          }
-      if (!found) {
-         Pmsg1(0, _("\"%s\" is an illegal command\n"), cmd);
+      if (*cmd && !found) {
+         Pmsg1(0, _("\"%s\" is an invalid command\n"), cmd);
       }
    }
 }
@@ -2625,17 +2955,18 @@ static void helpcmd()
 static void usage()
 {
    fprintf(stderr, _(
-"Copyright (C) 2000-%s Kern Sibbald.\n"
+PROG_COPYRIGHT
 "\nVersion: %s (%s)\n\n"
 "Usage: btape <options> <device_name>\n"
 "       -b <file>   specify bootstrap file\n"
 "       -c <file>   set configuration file to file\n"
-"       -d <nn>     set debug level to nn\n"
+"       -d <nn>     set debug level to <nn>\n"
+"       -dt         print timestamp in debug output\n"
 "       -p          proceed inspite of I/O errors\n"
 "       -s          turn off signals\n"
 "       -v          be verbose\n"
 "       -?          print this message.\n"
-"\n"), BYEAR, VERSION, BDATE);
+"\n"), 2000, VERSION, BDATE);
 
 }
 
@@ -2649,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.
@@ -2660,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;
       }
 
@@ -2676,7 +3009,7 @@ get_cmd(const char *prompt)
 bool    dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
 bool    dir_send_job_status(JCR *jcr) {return 1;}
 
-bool dir_update_volume_info(DCR *dcr, bool relabel)
+bool dir_update_volume_info(DCR *dcr, bool relabel, bool update_LastWritten)
 {
    return 1;
 }
@@ -2685,11 +3018,11 @@ bool dir_update_volume_info(DCR *dcr, bool relabel)
 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;
@@ -2702,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");
@@ -2724,7 +3057,7 @@ bool dir_ask_sysop_to_mount_volume(DCR *dcr)
 
 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr)
 {
-   bool autochanger;
+   int autochanger;
    DEVICE *dev = dcr->dev;
    Dmsg0(20, "Enter dir_ask_sysop_to_create_appendable_volume\n");
    if (stop == 0) {
@@ -2733,17 +3066,18 @@ bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr)
       set_volume_name("TestVolume2", 2);
    }
    /* Close device so user can use autochanger if desired */
-   if (dev_cap(dev, CAP_OFFLINEUNMOUNT)) {
+   if (dev->has_cap(CAP_OFFLINEUNMOUNT)) {
       dev->offline();
    }
    autochanger = autoload_device(dcr, 1, NULL);
-   if (!autochanger) {
+   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");
    }
-   open_device(dcr);
    labelcmd();
    VolumeName = NULL;
    BlockNumber = 0;
@@ -2752,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;
 
@@ -2760,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;
    }
@@ -2769,20 +3105,19 @@ 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;
       return false;
    }
 
-   free_restore_volume_list(jcr);
-   dev->close();
    set_volume_name("TestVolume2", 2);
-   jcr->bsr = NULL;
-   create_restore_volume_list(jcr);
+
+   dev->close();
    if (!acquire_device_for_read(dcr)) {
       Pmsg2(0, _("Cannot open Dev=%s, Vol=%s\n"), dev->print_name(), dcr->VolumeName);
       return false;
@@ -2795,8 +3130,9 @@ 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;
 }