]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/stored/btape.c
This commit was manufactured by cvs2svn to create tag
[bacula/bacula] / bacula / src / stored / btape.c
index 7b2c5642fedf6c3558b903e648f9b367de405462..e7d1426e6be407c1b74bf3a3c69a1022dd90f360 100644 (file)
@@ -46,6 +46,12 @@ char buf[100000];
 int bsize = TAPE_BSIZE;
 char VolName[MAX_NAME_LENGTH];
 
 int bsize = TAPE_BSIZE;
 char VolName[MAX_NAME_LENGTH];
 
+/*
+ * If you change the format of the state file, 
+ *  increment this value
+ */ 
+static uint32_t btape_state_level = 1;
+
 DEVICE *dev = NULL;
 DEVRES *device = NULL;
 
 DEVICE *dev = NULL;
 DEVRES *device = NULL;
 
@@ -70,14 +76,24 @@ static void scan_blocks();
 static void set_volume_name(char *VolName, int volnum);
 static void rawfill_cmd();
 static void bfill_cmd();
 static void set_volume_name(char *VolName, int volnum);
 static void rawfill_cmd();
 static void bfill_cmd();
+static bool open_the_device();
+static char *edit_device_codes(JCR *jcr, char *omsg, char *imsg, char *cmd);
+static void autochangercmd();
+static void do_unfill();
 
 
 /* Static variables */
 #define CONFIG_FILE "bacula-sd.conf"
 char *configfile;
 
 
 
 /* Static variables */
 #define CONFIG_FILE "bacula-sd.conf"
 char *configfile;
 
+#define MAX_CMD_ARGS 30
+static POOLMEM *cmd;
+static POOLMEM *args;
+static char *argk[MAX_CMD_ARGS];
+static char *argv[MAX_CMD_ARGS];
+static int argc;
+
 static BSR *bsr = NULL;
 static BSR *bsr = NULL;
-static char cmd[1000];
 static int signals = TRUE;
 static int ok;
 static int stop;
 static int signals = TRUE;
 static int ok;
 static int stop;
@@ -115,10 +131,9 @@ int get_cmd(char *prompt);
  *        Main Bacula Pool Creation Program
  *
  */
  *        Main Bacula Pool Creation Program
  *
  */
-int main(int argc, char *argv[])
+int main(int margc, char *margv[])
 {
    int ch;
 {
    int ch;
-   DEV_BLOCK *block;
 
    /* Sanity checks */
    if (TAPE_BSIZE % DEV_BSIZE != 0 || TAPE_BSIZE / DEV_BSIZE == 0) {
 
    /* Sanity checks */
    if (TAPE_BSIZE % DEV_BSIZE != 0 || TAPE_BSIZE / DEV_BSIZE == 0) {
@@ -132,10 +147,10 @@ int main(int argc, char *argv[])
    printf("Tape block granularity is %d bytes.\n", TAPE_BSIZE);
 
    working_directory = "/tmp";
    printf("Tape block granularity is %d bytes.\n", TAPE_BSIZE);
 
    working_directory = "/tmp";
-   my_name_is(argc, argv, "btape");
+   my_name_is(margc, margv, "btape");
    init_msg(NULL, NULL);
 
    init_msg(NULL, NULL);
 
-   while ((ch = getopt(argc, argv, "b:c:d:sv?")) != -1) {
+   while ((ch = getopt(margc, margv, "b:c:d:sv?")) != -1) {
       switch (ch) {
       case 'b':                    /* bootstrap file */
         bsr = parse_bsr(NULL, optarg);
       switch (ch) {
       case 'b':                    /* bootstrap file */
         bsr = parse_bsr(NULL, optarg);
@@ -171,10 +186,11 @@ int main(int argc, char *argv[])
 
       }  
    }
 
       }  
    }
-   argc -= optind;
-   argv += optind;
-
+   margc -= optind;
+   margv += optind;
 
 
+   cmd = get_pool_memory(PM_FNAME);
+   args = get_pool_memory(PM_FNAME);
    
    if (signals) {
       init_signals(terminate_btape);
    
    if (signals) {
       init_signals(terminate_btape);
@@ -190,35 +206,25 @@ int main(int argc, char *argv[])
 
 
    /* See if we can open a device */
 
 
    /* See if we can open a device */
-   if (argc == 0) {
+   if (margc == 0) {
       Pmsg0(000, "No archive name specified.\n");
       usage();
       exit(1);
       Pmsg0(000, "No archive name specified.\n");
       usage();
       exit(1);
-   } else if (argc != 1) {
+   } else if (margc != 1) {
       Pmsg0(000, "Improper number of arguments specified.\n");
       usage();
       exit(1);
    }
 
       Pmsg0(000, "Improper number of arguments specified.\n");
       usage();
       exit(1);
    }
 
-   jcr = setup_jcr("btape", argv[0], bsr, NULL);
+   jcr = setup_jcr("btape", margv[0], bsr, NULL);
    dev = setup_to_access_device(jcr, 0);     /* acquire for write */
    if (!dev) {
       exit(1);
    }
    dev = setup_to_access_device(jcr, 0);     /* acquire for write */
    if (!dev) {
       exit(1);
    }
-   block = new_block(dev);
-   lock_device(dev);
-   if (!(dev->state & ST_OPENED)) {
-      Dmsg0(129, "Opening device.\n");
-      if (open_dev(dev, jcr->VolumeName, READ_WRITE) < 0) {
-         Emsg1(M_FATAL, 0, _("dev open failed: %s\n"), dev->errmsg);
-        unlock_device(dev);
-        free_block(block);
-        goto terminate;
-      }
+   dev->max_volume_size = 0;        
+   if (!open_the_device()) {
+      goto terminate;
    }
    }
-   Dmsg1(129, "open_dev %s OK\n", dev_name(dev));
-   unlock_device(dev);
-   free_block(block);
 
    Dmsg0(200, "Do tape commands\n");
    do_tape_cmds();
 
    Dmsg0(200, "Do tape commands\n");
    do_tape_cmds();
@@ -236,6 +242,14 @@ static void terminate_btape(int stat)
       free(configfile);
    }
    free_config_resources();
       free(configfile);
    }
    free_config_resources();
+   if (args) {
+      free_pool_memory(args);
+      args = NULL;
+   }
+   if (cmd) {
+      free_pool_memory(cmd);
+      cmd = NULL;
+   }
 
    if (dev) {
       term_dev(dev);
 
    if (dev) {
       term_dev(dev);
@@ -265,6 +279,28 @@ static void terminate_btape(int stat)
    exit(stat);
 }
 
    exit(stat);
 }
 
+static bool open_the_device()
+{
+   DEV_BLOCK *block;
+   
+   block = new_block(dev);
+   lock_device(dev);
+   if (!(dev->state & ST_OPENED)) {
+      Dmsg1(200, "Opening device %s\n", jcr->VolumeName);
+      if (open_dev(dev, jcr->VolumeName, READ_WRITE) < 0) {
+         Emsg1(M_FATAL, 0, _("dev open failed: %s\n"), dev->errmsg);
+        unlock_device(dev);
+        free_block(block);
+        return false;
+      }
+   }
+   Dmsg1(000, "open_dev %s OK\n", dev_name(dev));
+   unlock_device(dev);
+   free_block(block);
+   return true;
+}
+
+
 void quitcmd()
 {
    quit = 1;
 void quitcmd()
 {
    quit = 1;
@@ -275,27 +311,8 @@ void quitcmd()
  */
 static void labelcmd()
 {
  */
 static void labelcmd()
 {
-   DEVRES *device;
-   int found = 0;
-
-   LockRes();
-   for (device=NULL; (device=(DEVRES *)GetNextRes(R_DEVICE, (RES *)device)); ) {
-      if (strcmp(device->device_name, dev->dev_name) == 0) {
-        jcr->device = device;        /* Arggg a bit of duplication here */
-        device->dev = dev;
-        dev->device = device;
-        found = 1;
-        break;
-      }
-   } 
-   UnlockRes();
-   if (!found) {
-      Pmsg2(0, "Could not find device %s in %s\n", dev->dev_name, configfile);
-      return;
-   }
-
    if (VolumeName) {
    if (VolumeName) {
-      strcpy(cmd, VolumeName);
+      pm_strcpy(&cmd, VolumeName);
    } else {
       if (!get_cmd("Enter Volume Name: ")) {
         return;
    } else {
       if (!get_cmd("Enter Volume Name: ")) {
         return;
@@ -307,7 +324,7 @@ static void labelcmd()
          Pmsg1(0, "Device open failed. ERR=%s\n", strerror_dev(dev));
       }
    }
          Pmsg1(0, "Device open failed. ERR=%s\n", strerror_dev(dev));
       }
    }
-   write_volume_label_to_dev(jcr, device, cmd, "Default");
+   write_volume_label_to_dev(jcr, jcr->device, cmd, "Default");
 }
 
 /*
 }
 
 /*
@@ -395,12 +412,19 @@ static void clearcmd()
 static void weofcmd()
 {
    int stat;
 static void weofcmd()
 {
    int stat;
+   int num = 1;
+   if (argc > 1) {
+      num = atoi(argk[1]);
+   }
+   if (num <= 0) {
+      num = 1;
+   }
 
 
-   if ((stat = weof_dev(dev, 1)) < 0) {
+   if ((stat = weof_dev(dev, num)) < 0) {
       Pmsg2(0, "Bad status from weof %d. ERR=%s\n", stat, strerror_dev(dev));
       return;
    } else {
       Pmsg2(0, "Bad status from weof %d. ERR=%s\n", stat, strerror_dev(dev));
       return;
    } else {
-      Pmsg1(0, "Wrote EOF to %s\n", dev_name(dev));
+      Pmsg3(0, "Wrote %d EOF%s to %s\n", num, num==1?"":"s", dev_name(dev));
    }
 }
 
    }
 }
 
@@ -414,7 +438,7 @@ static void weofcmd()
 static void eomcmd()
 {
    if (!eod_dev(dev)) {
 static void eomcmd()
 {
    if (!eod_dev(dev)) {
-      Pmsg1(0, _("Bad status from MTEOD. ERR=%s\n"), strerror_dev(dev));
+      Pmsg1(0, "%s", strerror_dev(dev));
       return;
    } else {
       Pmsg0(0, _("Moved to end of medium.\n"));
       return;
    } else {
       Pmsg0(0, _("Moved to end of medium.\n"));
@@ -435,12 +459,18 @@ static void eodcmd()
  */
 static void bsfcmd()
 {
  */
 static void bsfcmd()
 {
-   int stat;
+   int num = 1;
+   if (argc > 1) {
+      num = atoi(argk[1]);
+   }
+   if (num <= 0) {
+      num = 1;
+   }
 
 
-   if ((stat=bsf_dev(dev, 1)) < 0) {
-      Pmsg1(0, _("Bad status from bsf. ERR=%s\n"), strerror(errno));
+   if (!bsf_dev(dev, num)) {
+      Pmsg1(0, _("Bad status from bsf. ERR=%s\n"), strerror_dev(dev));
    } else {
    } else {
-      Pmsg0(0, _("Backspaced one file.\n"));
+      Pmsg2(0, _("Backspaced %d file%s.\n"), num, num==1?"":"s");
    }
 }
 
    }
 }
 
@@ -449,12 +479,17 @@ static void bsfcmd()
  */
 static void bsrcmd()
 {
  */
 static void bsrcmd()
 {
-   int stat;
-
-   if ((stat=bsr_dev(dev, 1)) < 0) {
-      Pmsg1(0, _("Bad status from bsr. ERR=%s\n"), strerror(errno));
+   int num = 1;
+   if (argc > 1) {
+      num = atoi(argk[1]);
+   }
+   if (num <= 0) {
+      num = 1;
+   }
+   if (!bsr_dev(dev, num)) {
+      Pmsg1(0, _("Bad status from bsr. ERR=%s\n"), strerror_dev(dev));
    } else {
    } else {
-      Pmsg0(0, _("Backspaced one record.\n"));
+      Pmsg2(0, _("Backspaced %d record%s.\n"), num, num==1?"":"s");
    }
 }
 
    }
 }
 
@@ -464,7 +499,7 @@ static void bsrcmd()
  */
 static void capcmd()
 {
  */
 static void capcmd()
 {
-   printf(_("Device capabilities:\n"));
+   printf(_("Configured device capabilities:\n"));
    printf("%sEOF ", dev->capabilities & CAP_EOF ? "" : "!");
    printf("%sBSR ", dev->capabilities & CAP_BSR ? "" : "!");
    printf("%sBSF ", dev->capabilities & CAP_BSF ? "" : "!");
    printf("%sEOF ", dev->capabilities & CAP_EOF ? "" : "!");
    printf("%sBSR ", dev->capabilities & CAP_BSR ? "" : "!");
    printf("%sBSF ", dev->capabilities & CAP_BSF ? "" : "!");
@@ -568,8 +603,8 @@ static int re_read_block_test()
    }
 
    Pmsg0(-1, _("\n=== Write, backup, and re-read test ===\n\n"
    }
 
    Pmsg0(-1, _("\n=== Write, backup, and re-read test ===\n\n"
-      "I'm going to write three records and two eof's\n"
-      "then backup over the eof's and re-read the last record.\n"     
+      "I'm going to write three records and an eof\n"
+      "then backup over the eof and re-read the last record.\n"     
       "Bacula does this after writing the last block on the\n"
       "tape to verify that the block was written correctly.\n"
       "It is not an *essential* feature ...\n\n")); 
       "Bacula does this after writing the last block on the\n"
       "tape to verify that the block was written correctly.\n"
       "It is not an *essential* feature ...\n\n")); 
@@ -612,18 +647,22 @@ static int re_read_block_test()
       Pmsg1(0, _("Wrote third record of %d bytes.\n"), rec->data_len);
    }
    weofcmd();
       Pmsg1(0, _("Wrote third record of %d bytes.\n"), rec->data_len);
    }
    weofcmd();
-   weofcmd();
-   if (bsf_dev(dev, 1) != 0) {
-      Pmsg1(0, _("Backspace file failed! ERR=%s\n"), strerror(dev->dev_errno));
-      goto bail_out;
+   if (dev_cap(dev, CAP_TWOEOF)) {
+      weofcmd();
    }
    }
-   if (bsf_dev(dev, 1) != 0) {
-      Pmsg1(0, _("Backspace file failed! ERR=%s\n"), strerror(dev->dev_errno));
+   if (!bsf_dev(dev, 1)) {
+      Pmsg1(0, _("Backspace file failed! ERR=%s\n"), strerror_dev(dev));
       goto bail_out;
    }
       goto bail_out;
    }
-   Pmsg0(0, "Backspaced over two EOFs OK.\n");
-   if (bsr_dev(dev, 1) != 0) {
-      Pmsg1(0, _("Backspace record failed! ERR=%s\n"), strerror(dev->dev_errno));
+   if (dev_cap(dev, CAP_TWOEOF)) {
+      if (!bsf_dev(dev, 1)) {
+         Pmsg1(0, _("Backspace file failed! ERR=%s\n"), strerror_dev(dev));
+        goto bail_out;
+      }
+   }
+   Pmsg0(0, "Backspaced over EOF OK.\n");
+   if (!bsr_dev(dev, 1)) {
+      Pmsg1(0, _("Backspace record failed! ERR=%s\n"), strerror_dev(dev));
       goto bail_out;
    }
    Pmsg0(0, "Backspace record OK.\n");
       goto bail_out;
    }
    Pmsg0(0, "Backspace record OK.\n");
@@ -674,6 +713,7 @@ static int append_test()
 "I'm going to write one record  in file 0,\n"
 "                   two records in file 1,\n"
 "             and three records in file 2\n\n"));
 "I'm going to write one record  in file 0,\n"
 "                   two records in file 1,\n"
 "             and three records in file 2\n\n"));
+   argc = 1;
    rewindcmd();
    wrcmd();
    weofcmd();     /* end file 0 */
    rewindcmd();
    wrcmd();
    weofcmd();     /* end file 0 */
@@ -712,6 +752,226 @@ static int append_test()
    return 1;
 }
 
    return 1;
 }
 
+
+/*
+ * This test exercises the autochanger
+ */
+static int autochanger_test()
+{
+   POOLMEM *results, *changer;
+   int slot, status, loaded;
+   int timeout = 120;
+   int sleep_time = 0;
+
+   if (!dev_cap(dev, CAP_AUTOCHANGER)) {
+      return 1;
+   }
+   if (!(jcr->device && jcr->device->changer_name && jcr->device->changer_command)) {
+      Pmsg0(-1, "\nAutochanger enabled, but no name or no command device specified.\n");
+      return 1;
+   }
+
+   Pmsg0(-1, "\nTo test the autochanger you must have a blank tape in Slot 1.\n"
+             "I'm going to write on it.\n");
+   if (!get_cmd("\nDo you wish to continue with the Autochanger test? (y/n): ")) {
+      return 0;
+   }
+   if (cmd[0] != 'y' && cmd[0] != 'Y') {
+      return 0;
+   }
+
+   Pmsg0(-1, _("\n\n=== Autochanger test ===\n\n"));
+
+   results = get_pool_memory(PM_MESSAGE);
+   changer = get_pool_memory(PM_FNAME);
+
+try_again:
+   slot = 1;
+   jcr->VolCatInfo.Slot = slot;
+   /* Find out what is loaded, zero means device is unloaded */
+   Pmsg0(-1, _("3301 Issuing autochanger \"loaded\" command.\n"));
+   changer = edit_device_codes(jcr, changer, jcr->device->changer_command, 
+                "loaded");
+   status = run_program(changer, timeout, results);
+   Dmsg3(100, "run_prog: %s stat=%d result=%s\n", changer, status, results);
+   if (status == 0) {
+      loaded = atoi(results);
+   } else {
+      Pmsg1(-1, _("3991 Bad autochanger \"load slot\" status=%d.\n"), status);
+      loaded = -1;             /* force unload */
+      goto bail_out;
+   }
+   if (loaded) {
+      Pmsg1(-1, "Slot %d loaded. I am going to unload it.\n", loaded);
+   } else {
+      Pmsg0(-1, "Nothing loaded into the drive. OK.\n");
+   }
+   Dmsg1(100, "Results from loaded query=%s\n", results);
+   if (loaded) {
+      offline_or_rewind_dev(dev);
+      /* We are going to load a new tape, so close the device */
+      force_close_dev(dev);
+      Pmsg0(-1, _("3302 Issuing autochanger \"unload\" command.\n"));
+      changer = edit_device_codes(jcr, changer, 
+                     jcr->device->changer_command, "unload");
+      status = run_program(changer, timeout, NULL);
+      Pmsg2(-1, "unload status=%s %d\n", status==0?"OK":"Bad", status);
+   }
+
+   /*
+    * Load the Slot 1
+    */
+   Pmsg1(-1, _("3303 Issuing autochanger \"load slot %d\" command.\n"), slot);
+   changer = edit_device_codes(jcr, changer, jcr->device->changer_command, "load");
+   Dmsg1(200, "Changer=%s\n", changer);
+   status = run_program(changer, timeout, NULL);
+   if (status == 0) {
+      Pmsg1(-1,  _("3304 Autochanger \"load slot %d\" status is OK.\n"), slot);
+   } else {
+      Pmsg1(-1,  _("3992 Bad autochanger \"load slot\" status=%d.\n"), status);
+      goto bail_out;
+   }
+
+   if (!open_the_device()) {
+      goto bail_out;
+   }
+   bmicrosleep(sleep_time, 0);
+   if (!rewind_dev(dev)) {
+      Pmsg1(0, "Bad status from rewind. ERR=%s\n", strerror_dev(dev));
+      clrerror_dev(dev, -1);
+      Pmsg0(-1, "\nThe test failed, probably because you need to put\n"
+                "a longer sleep time in the mtx-script in the load) case.\n" 
+                "Adding a 30 second sleep and trying again ...\n");
+      sleep_time += 30;
+      goto try_again;
+   } else {
+      Pmsg1(0, "Rewound %s\n", dev_name(dev));
+   }
+      
+   if ((status = weof_dev(dev, 1)) < 0) {
+      Pmsg2(0, "Bad status from weof %d. ERR=%s\n", status, strerror_dev(dev));
+      goto bail_out;
+   } else {
+      Pmsg1(0, "Wrote EOF to %s\n", dev_name(dev));
+   }
+
+   if (sleep_time) {
+      Pmsg1(-1, "\nThe test worked this time. Please add:\n\n"
+                "   sleep %d\n\n"
+                "to your mtx-changer script in the load) case.\n\n",
+               sleep_time);
+   } else {
+      Pmsg0(-1, "\nThe test autochanger worked!!\n\n");
+   }
+
+   free_pool_memory(changer);
+   free_pool_memory(results);
+   return 1;
+
+
+bail_out:
+   free_pool_memory(changer);
+   free_pool_memory(results);
+   Pmsg0(-1, "You must correct this error or the Autochanger will not work.\n");
+   return -2;
+}
+
+static void autochangercmd()
+{
+   autochanger_test();
+}
+
+
+/*
+ * This test assumes that the append test has been done,
+ *   then it tests the fsf function.
+ */
+static int fsf_test()
+{
+   bool set_off = false;
+   
+   Pmsg0(-1, _("\n\n=== Forward space files test ===\n\n"
+               "This test is essential to Bacula.\n\n"
+               "I'm going to write five files then test forward spacing\n\n"));
+   argc = 1;
+   rewindcmd();
+   wrcmd();
+   weofcmd();     /* end file 0 */
+   wrcmd();
+   wrcmd();
+   weofcmd();     /* end file 1 */
+   wrcmd();
+   wrcmd();
+   wrcmd();
+   weofcmd();    /* end file 2 */
+   wrcmd();
+   wrcmd();
+   weofcmd();    /* end file 3 */
+   wrcmd();
+   weofcmd();    /* end file 4 */
+
+test_again:
+   rewindcmd();
+   Pmsg0(0, _("Now forward spacing 1 file.\n"));
+   if (!fsf_dev(dev, 1)) {
+      Pmsg1(0, "Bad status from fsr. ERR=%s\n", strerror_dev(dev));
+      goto bail_out;
+   }
+   Pmsg2(-1, _("We should be in file 1. I am at file %d. This is %s\n"), 
+      dev->file, dev->file == 1 ? "correct!" : "NOT correct!!!!");
+
+   if (dev->file != 1) {
+      goto bail_out;
+   }
+
+   Pmsg0(0, _("Now forward spacing 2 files.\n"));
+   if (!fsf_dev(dev, 2)) {
+      Pmsg1(0, "Bad status from fsr. ERR=%s\n", strerror_dev(dev));
+      goto bail_out;
+   }
+   Pmsg2(-1, _("We should be in file 3. I am at file %d. This is %s\n"), 
+      dev->file, dev->file == 3 ? "correct!" : "NOT correct!!!!");
+
+   if (dev->file != 3) {
+      goto bail_out;
+   }
+
+   rewindcmd();
+   Pmsg0(0, _("Now forward spacing 4 files.\n"));
+   if (!fsf_dev(dev, 4)) {
+      Pmsg1(0, "Bad status from fsr. ERR=%s\n", strerror_dev(dev));
+      goto bail_out;
+   }
+   Pmsg2(-1, _("We should be in file 4. I am at file %d. This is %s\n"), 
+      dev->file, dev->file == 4 ? "correct!" : "NOT correct!!!!");
+
+   if (dev->file != 4) {
+      goto bail_out;
+   }
+   if (set_off) {
+      Pmsg0(-1, "The test worked this time. Please add:\n\n"
+                "   Fast Forward Space File = no\n\n"
+                "to your Device resource for this drive.\n");
+   }
+   return 1;
+
+bail_out:
+   Pmsg0(-1, _("\nThe forward space file test failed.\n"));
+   if (dev_cap(dev, CAP_FASTFSF)) {
+      Pmsg0(-1, "You have Fast Forward Space File enabled.\n"
+              "I am turning it off then retring the test.\n");
+      dev->capabilities &= ~CAP_FASTFSF;
+      set_off = true;
+      goto test_again;
+   }
+   Pmsg0(-1, "You must correct this error or Bacula will not work.\n");
+   return -2;
+}
+
+
+
+
+
 /* 
  * This is a general test of Bacula's functions
  *   needed to read and write the tape.
 /* 
  * This is a general test of Bacula's functions
  *   needed to read and write the tape.
@@ -727,12 +987,16 @@ static void testcmd()
    if (stat == -1) {                 /* first test failed */
       if (dev_cap(dev, CAP_EOM)) {
          Pmsg0(-1, "\nAppend test failed. Attempting again.\n"
    if (stat == -1) {                 /* first test failed */
       if (dev_cap(dev, CAP_EOM)) {
          Pmsg0(-1, "\nAppend test failed. Attempting again.\n"
-                   "Setting \"Hardware End of Medium = no\" and retrying append test.\n\n");
+                   "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_EOM; /* turn off eom */
+        dev->capabilities &= ~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"
                      "    Hardware End of Medium = No\n\n"
         stat = append_test();
         if (stat == 1) {
             Pmsg0(-1, "\n\nIt looks like the test worked this time, please add:\n\n"
                      "    Hardware End of Medium = No\n\n"
+                     "    Fast Forward Space File = No\n"
                      "to your Device resource in the Storage conf file.\n");
            goto all_done;
         }
                      "to your Device resource in the Storage conf file.\n");
            goto all_done;
         }
@@ -749,23 +1013,25 @@ static void testcmd()
            if (stat == 1) {
                Pmsg0(-1, "\n\nIt looks like the test worked this time, please add:\n\n"
                      "    Hardware End of Medium = No\n"
            if (stat == 1) {
                Pmsg0(-1, "\n\nIt looks like the test worked this time, please add:\n\n"
                      "    Hardware End of Medium = No\n"
+                     "    Fast Forward Space File = No\n"
                      "    BSF at EOM = yes\n\n"
                      "to your Device resource in the Storage conf file.\n");
               goto all_done;
            }
         }
 
                      "    BSF at EOM = yes\n\n"
                      "to your Device resource in the Storage conf file.\n");
               goto all_done;
            }
         }
 
-         Pmsg0(-1, "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
-               "Unable to correct the problem. You MUST fix this\n"
-                "problem before Bacula can use your tape drive correctly\n");
-         Pmsg0(-1, "\nPerhaps running Bacula in fixed block mode will work.\n"
-               "Do so by setting:\n\n"
-               "Minimum Block Size = nnn\n"
-               "Maximum Block Size = nnn\n\n"
-               "in your Storage daemon's Device definition.\n"
-               "nnn must match your tape driver's block size.\n"
-               "This, however, is not really an ideal solution.\n");
       }
       }
+      Pmsg0(-1, "\nAppend test failed.\n\n");
+      Pmsg0(-1, "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
+            "Unable to correct the problem. You MUST fix this\n"
+             "problem before Bacula can use your tape drive correctly\n");
+      Pmsg0(-1, "\nPerhaps running Bacula in fixed block mode will work.\n"
+            "Do so by setting:\n\n"
+            "Minimum Block Size = nnn\n"
+            "Maximum Block Size = nnn\n\n"
+            "in your Storage daemon's Device definition.\n"
+            "nnn must match your tape driver's block size.\n"
+            "This, however, is not really an ideal solution.\n");
    }
 
 all_done:
    }
 
 all_done:
@@ -780,7 +1046,7 @@ all_done:
         "End of File mark.\n"
         "1 block of 64448 bytes in file 4\n" 
         "End of File mark.\n"
         "End of File mark.\n"
         "1 block of 64448 bytes in file 4\n" 
         "End of File mark.\n"
-        "Total files=4, blocks=7, bytes = 451136\n"
+        "Total files=4, blocks=7, bytes = 451,136\n"
         "=== End sample correct output ===\n\n"));
 
    Pmsg0(-1, _("If the above scan output is not identical to the\n"
         "=== End sample correct output ===\n\n"));
 
    Pmsg0(-1, _("If the above scan output is not identical to the\n"
@@ -792,6 +1058,10 @@ all_done:
       re_read_block_test();
    }
 
       re_read_block_test();
    }
 
+   fsf_test();                       /* do fast forward space file test */
+
+   autochanger_test();               /* do autochanger test */
+
    Pmsg0(-1, _("\n=== End Append files test ===\n"));
    
 }
    Pmsg0(-1, _("\n=== End Append files test ===\n"));
    
 }
@@ -799,21 +1069,35 @@ all_done:
 /* Forward space a file */
 static void fsfcmd()
 {
 /* Forward space a file */
 static void fsfcmd()
 {
-   if (!fsf_dev(dev, 1)) {
+   int num = 1;
+   if (argc > 1) {
+      num = atoi(argk[1]);
+   }
+   if (num <= 0) {
+      num = 1;
+   }
+   if (!fsf_dev(dev, num)) {
       Pmsg1(0, "Bad status from fsf. ERR=%s\n", strerror_dev(dev));
       return;
    }
       Pmsg1(0, "Bad status from fsf. ERR=%s\n", strerror_dev(dev));
       return;
    }
-   Pmsg0(0, "Forward spaced one file.\n");
+   Pmsg2(0, "Forward spaced %d file%s.\n", num, num==1?"":"s");
 }
 
 /* Forward space a record */
 static void fsrcmd()
 {
 }
 
 /* Forward space a record */
 static void fsrcmd()
 {
-   if (!fsr_dev(dev, 1)) {
+   int num = 1;
+   if (argc > 1) {
+      num = atoi(argk[1]);
+   }
+   if (num <= 0) {
+      num = 1;
+   }
+   if (!fsr_dev(dev, num)) {
       Pmsg1(0, "Bad status from fsr. ERR=%s\n", strerror_dev(dev));
       return;
    }
       Pmsg1(0, "Bad status from fsr. ERR=%s\n", strerror_dev(dev));
       return;
    }
-   Pmsg0(0, "Forward spaced one record.\n");
+   Pmsg2(0, "Forward spaced %d record%s.\n", num, num==1?"":"s");
 }
 
 
 }
 
 
@@ -1071,10 +1355,16 @@ static void fillcmd()
    DEV_RECORD rec;
    DEV_BLOCK  *block;
    char ec1[50];
    DEV_RECORD rec;
    DEV_BLOCK  *block;
    char ec1[50];
+   int fd;
+   uint32_t i;
+   uint32_t min_block_size;
 
    ok = TRUE;
    stop = 0;
    vol_num = 0;
 
    ok = TRUE;
    stop = 0;
    vol_num = 0;
+   last_file = 0;
+   last_block_num = 0;
+   BlockNumber = 0;
 
    Pmsg0(-1, "\n\
 This command simulates Bacula writing to a tape.\n\
 
    Pmsg0(-1, "\n\
 This command simulates Bacula writing to a tape.\n\
@@ -1110,6 +1400,10 @@ This may take a long time -- hours! ...\n\n");
    
    Dmsg1(20, "Begin append device=%s\n", dev_name(dev));
 
    
    Dmsg1(20, "Begin append device=%s\n", dev_name(dev));
 
+
+   /* Use fixed block size to simplify read back */
+   min_block_size = dev->min_block_size;
+   dev->min_block_size = dev->max_block_size;
    block = new_block(dev);
 
    /* 
    block = new_block(dev);
 
    /* 
@@ -1142,29 +1436,45 @@ This may take a long time -- hours! ...\n\n");
 #define REC_SIZE 32768
    rec.data_len = REC_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();
+      }
+   }
+
    /* 
     * Generate data as if from File daemon, write to device   
     */
    jcr->VolFirstIndex = 0;
    time(&jcr->run_time);             /* start counting time for rates */
    /* 
     * Generate data as if from File daemon, write to device   
     */
    jcr->VolFirstIndex = 0;
    time(&jcr->run_time);             /* start counting time for rates */
-   Pmsg0(-1, "Begin writing Bacula records to first tape ...\n");
-   Pmsg1(-1, "Block num = %d\n", dev->block_num);
+   if (simple) {
+      Pmsg0(-1, "Begin writing Bacula records to tape ...\n");
+   } else {
+      Pmsg0(-1, "Begin writing Bacula records to first tape ...\n");
+   }
    for (file_index = 0; ok && !job_canceled(jcr); ) {
       rec.VolSessionId = jcr->VolSessionId;
       rec.VolSessionTime = jcr->VolSessionTime;
       rec.FileIndex = ++file_index;
       rec.Stream = STREAM_FILE_DATA;
 
    for (file_index = 0; ok && !job_canceled(jcr); ) {
       rec.VolSessionId = jcr->VolSessionId;
       rec.VolSessionTime = jcr->VolSessionTime;
       rec.FileIndex = ++file_index;
       rec.Stream = STREAM_FILE_DATA;
 
-      /* 
-       * Fill the buffer with the file_index negated. Negation ensures that
-       *   more bits are turned on.
-       */
-      uint64_t *lp = (uint64_t *)rec.data;
-      for (uint32_t i=0; i < (rec.data_len-sizeof(uint64_t))/sizeof(uint64_t); i++) {
-        *lp++ = ~file_index;
+      /* 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];
       }
 
       }
 
-      Dmsg4(250, "before writ_rec FI=%d SessId=%d Strm=%s len=%d\n",
+      Dmsg4(250, "before write_rec FI=%d SessId=%d Strm=%s len=%d\n",
         rec.FileIndex, rec.VolSessionId, stream_to_ascii(rec.Stream, rec.FileIndex), 
         rec.data_len);
        
         rec.FileIndex, rec.VolSessionId, stream_to_ascii(rec.Stream, rec.FileIndex), 
         rec.data_len);
        
@@ -1186,11 +1496,11 @@ This may take a long time -- hours! ...\n\n");
            now = time(NULL);
            now -= jcr->run_time;
            if (now <= 0) {
            now = time(NULL);
            now -= jcr->run_time;
            if (now <= 0) {
-              now = 1;
+              now = 1;          /* prevent divide error */
            }
            kbs = (double)dev->VolCatInfo.VolCatBytes / (1000.0 * (double)now);
            }
            kbs = (double)dev->VolCatInfo.VolCatBytes / (1000.0 * (double)now);
-            Pmsg4(-1, "Wrote block=%u, blk_num=%d VolBytes=%s rate=%.1f KB/s\n", block->BlockNumber,
-              dev->block_num,
+            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);
         }
         /* Every 15000 blocks (approx 1GB) write an EOF.
               edit_uint64_with_commas(dev->VolCatInfo.VolCatBytes, ec1), (float)kbs);
         }
         /* Every 15000 blocks (approx 1GB) write an EOF.
@@ -1238,7 +1548,21 @@ This may take a long time -- hours! ...\n\n");
          Pmsg0(-1, _("Set ok=FALSE after write_block_to_device.\n"));
         ok = FALSE;
       }
          Pmsg0(-1, _("Set ok=FALSE after write_block_to_device.\n"));
         ok = FALSE;
       }
-      Pmsg0(-1, "Wrote End Of Session label.\n");
+      Pmsg0(-1, _("Wrote End Of Session label.\n"));
+   }
+
+   sprintf(buf, "%s/btape.state", working_directory);
+   fd = open(buf, O_CREAT|O_TRUNC|O_WRONLY, 0640);
+   if (fd >= 0) {
+      write(fd, &btape_state_level, sizeof(btape_state_level));
+      write(fd, &last_block_num, sizeof(last_block_num));
+      write(fd, &last_file, sizeof(last_file));
+      write(fd, last_block->buf, last_block->buf_len);
+      close(fd);
+      Pmsg0(-1, "Wrote state file.\n");
+   } else {
+      Pmsg2(-1, _("Could not create state file: %s ERR=%s\n"), buf,
+                strerror(errno));
    }
 
    /* Release the device */
    }
 
    /* Release the device */
@@ -1247,14 +1571,55 @@ This may take a long time -- hours! ...\n\n");
       ok = FALSE;
    }
 
       ok = FALSE;
    }
 
-   free_block(block);
-   free_memory(rec.data);
-
-   dump_block(last_block, _("Last block written to tape.\n"));
+   if (verbose) {
+      Pmsg0(-1, "\n");
+      dump_block(last_block, _("Last block written to tape.\n"));
+   }
 
    Pmsg0(-1, _("\n\nDone filling tape. Now beginning re-read of tape ...\n"));
 
 
    Pmsg0(-1, _("\n\nDone filling tape. Now beginning re-read of tape ...\n"));
 
-   unfillcmd();
+   if (simple) {
+      do_unfill();
+    } else {
+       /* Multiple Volume tape */
+      dumped = 0;
+      VolBytes = 0;
+      LastBlock = 0;
+      block = new_block(dev);
+
+      dev->capabilities |= CAP_ANONVOLS; /* allow reading any volume */
+      dev->capabilities &= ~CAP_LABEL;   /* don't label anything here */
+
+      end_of_tape = 0;
+
+
+      time(&jcr->run_time);             /* start counting time for rates */
+      stop = 0;
+      file_index = 0;
+      /* Close device so user can use autochanger if desired */
+      if (dev_cap(dev, CAP_OFFLINEUNMOUNT)) {
+        offline_dev(dev);
+      }
+      force_close_dev(dev);
+      get_cmd(_("Mount first tape. Press enter when ready: ")); 
+   
+      free_vol_list(jcr);
+      set_volume_name("TestVolume1", 1);
+      jcr->bsr = NULL;
+      create_vol_list(jcr);
+      close_dev(dev);
+      dev->state &= ~ST_READ;
+      if (!acquire_device_for_read(jcr, dev, block)) {
+         Pmsg1(-1, "%s", dev->errmsg);
+        goto bail_out;
+      }
+      /* Read all records and then second tape */
+      read_records(jcr, dev, record_cb, my_mount_next_read_volume);
+   }
+bail_out:
+   dev->min_block_size = min_block_size;
+   free_block(block);
+   free_memory(rec.data);
 }
 
 /*
 }
 
 /*
@@ -1264,6 +1629,36 @@ This may take a long time -- hours! ...\n\n");
  *  verify that it is correct.
  */
 static void unfillcmd()
  *  verify that it is correct.
  */
 static void unfillcmd()
+{
+   int fd;
+
+   if (!last_block) {
+      last_block = new_block(dev);
+   }
+   sprintf(buf, "%s/btape.state", working_directory);
+   fd = open(buf, O_RDONLY);
+   if (fd >= 0) {
+      uint32_t state_level;             
+      read(fd, &state_level, sizeof(btape_state_level));
+      read(fd, &last_block_num, sizeof(last_block_num));
+      read(fd, &last_file, sizeof(last_file));
+      read(fd, last_block->buf, last_block->buf_len);
+      close(fd);
+      if (state_level != btape_state_level) {
+          Pmsg0(-1, "\nThe state file level has changed. You must redo\n"
+                  "the fill command.\n");
+         return;
+       }
+   } else {
+      Pmsg2(-1, "\nCould not find the state file: %s ERR=%s\n"
+             "You must redo the fill command.\n", buf, strerror(errno));
+      return;
+   }
+   
+   do_unfill();
+}
+
+static void do_unfill()
 {
    DEV_BLOCK *block;
 
 {
    DEV_BLOCK *block;
 
@@ -1277,75 +1672,61 @@ static void unfillcmd()
 
    end_of_tape = 0;
 
 
    end_of_tape = 0;
 
-   if (!simple) {
-      /* Close device so user can use autochanger if desired */
-      if (dev_cap(dev, CAP_OFFLINEUNMOUNT)) {
-        offline_dev(dev);
-      }
-      force_close_dev(dev);
-      get_cmd(_("Mount first tape. Press enter when ready: ")); 
-   
-      free_vol_list(jcr);
-      set_volume_name("TestVolume1", 1);
-      jcr->bsr = NULL;
-      create_vol_list(jcr);
-      close_dev(dev);
-      dev->state &= ~ST_READ;
-      if (!acquire_device_for_read(jcr, dev, block)) {
-         Pmsg1(-1, "%s", dev->errmsg);
-        return;
-      }
-   }
 
    time(&jcr->run_time);             /* start counting time for rates */
    stop = 0;
    file_index = 0;
 
    time(&jcr->run_time);             /* start counting time for rates */
    stop = 0;
    file_index = 0;
-   if (!simple) {
-      /* Read all records and then second tape */
-      read_records(jcr, dev, record_cb, my_mount_next_read_volume);
-   } else {
-      /*
-       * Simplified test, we simply fsf to file, then read the
-       * last block and make sure it is the same as the saved block.
-       */
-      Pmsg0(000, "Rewinding tape ...\n");
-      if (!rewind_dev(dev)) {
-         Pmsg1(-1, _("Error rewinding: ERR=%s\n"), strerror_dev(dev));
-        goto bail_out;
-      }
-      if (last_file > 0) {
-         Pmsg1(000, "Forward spacing to last file=%u\n", last_file);
-        if (!fsf_dev(dev, last_file)) {
-            Pmsg1(-1, _("Error in FSF: ERR=%s\n"), strerror_dev(dev));
-           goto bail_out;
-        }
-      }
-      Pmsg1(-1, _("Forward space to file %u complete. Reading blocks ...\n"), 
-           last_file);
-      Pmsg1(-1, _("Now reading to block %u.\n"), last_block_num);
-      for (uint32_t i=0; i <= last_block_num; i++) {
-        if (!read_block_from_device(jcr, dev, block, NO_BLOCK_NUMBER_CHECK)) {
-            Pmsg1(-1, _("Error reading blocks: ERR=%s\n"), strerror_dev(dev));
-            Pmsg2(-1, _("Wanted block %u error at block %u\n"), last_block_num, i);
-           goto bail_out;
-        }
-        if (i > 0 && i % 1000 == 0) {
-            Pmsg1(-1, _("At block %u\n"), i);
+
+   /*
+    * Note, re-reading last block may have caused us to 
+    *  lose track of where we are (block number unknown).
+    */
+   rewind_dev(dev);                  /* get to a known place on tape */
+   Pmsg4(-1, _("Reposition from %u:%u to %u:%u\n"), dev->file, dev->block_num,
+        last_file, last_block_num);
+   if (!reposition_dev(dev, last_file, last_block_num)) {
+      Pmsg1(-1, "Reposition error. ERR=%s\n", strerror_dev(dev));
+   }
+   Pmsg1(-1, _("Reading block %u.\n"), last_block_num);
+   if (!read_block_from_device(jcr, dev, block, NO_BLOCK_NUMBER_CHECK)) {
+      Pmsg1(-1, _("Error reading block: ERR=%s\n"), strerror_dev(dev));
+      goto bail_out;
+   }
+   if (last_block) {
+      char *p, *q;
+      uint32_t CheckSum, block_len;
+      ser_declare;
+      p = last_block->buf;     
+      q = block->buf;
+      unser_begin(q, BLKHDR2_LENGTH);
+      unser_uint32(CheckSum);
+      unser_uint32(block_len);
+      while (q < (block->buf+block_len)) {
+        if (*p == *q) {
+           p++;
+           q++;
+           continue;
         }
         }
+         Pmsg0(-1, "\n");
+         dump_block(last_block, _("Last block written"));
+         Pmsg0(-1, "\n");
+         dump_block(block, _("Block read back"));
+         Pmsg1(-1, "\n\nThe blocks differ at byte %u\n", p - last_block->buf);
+         Pmsg0(-1, "\n\n!!!! The last block written and the block\n"
+                   "that was read back differ. The test FAILED !!!!\n"
+                   "This must be corrected before you use Bacula\n"
+                   "to write multi-tape Volumes.!!!!\n");
+        goto bail_out;
       }
       }
-      if (last_block) {
+      Pmsg0(-1, _("\nThe blocks are identical. Test succeeded.\n\n"));
+      if (verbose) {
          dump_block(last_block, _("Last block written"));
          dump_block(block, _("Block read back"));
          dump_block(last_block, _("Last block written"));
          dump_block(block, _("Block read back"));
-         Pmsg0(-1, _("Except for the buffer address, the contents of\n"
-                     "the above two block dumps should be the same.\n"
-                     "If not you have a problem ...\n"));
       }
    }
 
 bail_out:
    free_block(block);
       }
    }
 
 bail_out:
    free_block(block);
-
-   Pmsg0(000, _("Done with reread of fill data.\n"));
 }
 
 /* 
 }
 
 /* 
@@ -1357,7 +1738,9 @@ static int record_cb(JCR *jcr, DEVICE *dev, DEV_BLOCK *block, DEV_RECORD *rec)
 
    if (stop > 1 && !dumped) {        /* on second tape */
       dumped = 1;
 
    if (stop > 1 && !dumped) {        /* on second tape */
       dumped = 1;
-      dump_block(block, "First block on second tape");
+      if (verbose) {
+         dump_block(block, "First block on second tape");
+      }
       Pmsg4(-1, "Blk: FileIndex=%d: block=%u size=%d vol=%s\n", 
           rec->FileIndex, block->BlockNumber, block->block_len, dev->VolHdr.VolName);
       Pmsg6(-1, "   Rec: VId=%d VT=%d FI=%s Strm=%s len=%d state=%x\n",
       Pmsg4(-1, "Blk: FileIndex=%d: block=%u size=%d vol=%s\n", 
           rec->FileIndex, block->BlockNumber, block->block_len, dev->VolHdr.VolName);
       Pmsg6(-1, "   Rec: VId=%d VT=%d FI=%s Strm=%s len=%d state=%x\n",
@@ -1474,16 +1857,17 @@ static int flush_block(DEV_BLOCK *block, int dump)
    free_memory(this_block->buf);    
    memcpy(this_block, block, sizeof(DEV_BLOCK));
    this_block->buf = get_memory(block->buf_len);
    free_memory(this_block->buf);    
    memcpy(this_block, block, sizeof(DEV_BLOCK));
    this_block->buf = get_memory(block->buf_len);
-   memcpy(this_block->buf, block->buf, this_block->buf_len);
    this_file = dev->file;
    this_block_num = dev->block_num;
    if (!write_block_to_dev(jcr, dev, block)) {
    this_file = dev->file;
    this_block_num = dev->block_num;
    if (!write_block_to_dev(jcr, dev, block)) {
-      Pmsg3(000, "Block not written: FileIndex=%u Block=%u Size=%u\n", 
-        (unsigned)file_index, block->BlockNumber, block->block_len);
-      Pmsg2(000, "last_block_num=%u this_block_num=%d\n", last_block_num,
-        this_block_num);
-      if (dump) {
-         dump_block(block, "Block not written");
+      Pmsg3(000, "Last block at: %u:%u this_dev_block_num=%d\n", 
+                 last_file, last_block_num, this_block_num);
+      if (verbose) {
+         Pmsg3(000, "Block not written: FileIndex=%u blk_block=%u Size=%u\n", 
+           (unsigned)file_index, block->BlockNumber, block->block_len);
+        if (dump) {
+            dump_block(block, "Block not written");
+        }
       }
       if (stop == 0) {
         eot_block = block->BlockNumber;
       }
       if (stop == 0) {
         eot_block = block->BlockNumber;
@@ -1493,7 +1877,7 @@ static int flush_block(DEV_BLOCK *block, int dump)
       now = time(NULL);
       now -= jcr->run_time;
       if (now <= 0) {
       now = time(NULL);
       now -= jcr->run_time;
       if (now <= 0) {
-        now = 1;
+         now = 1;                     /* don't divide by zero */
       }
       kbs = (double)dev->VolCatInfo.VolCatBytes / (1000 * now);
       vol_size = dev->VolCatInfo.VolCatBytes;
       }
       kbs = (double)dev->VolCatInfo.VolCatBytes / (1000 * now);
       vol_size = dev->VolCatInfo.VolCatBytes;
@@ -1516,6 +1900,8 @@ static int flush_block(DEV_BLOCK *block, int dump)
       unlock_device(dev);
       return 1;                      /* end of tape reached */
    }
       unlock_device(dev);
       return 1;                      /* end of tape reached */
    }
+   /* Save contents after write so that the header is serialized */
+   memcpy(this_block->buf, block->buf, this_block->buf_len);
 
    /*
     * Toggle between two allocated blocks for efficiency.
 
    /*
     * Toggle between two allocated blocks for efficiency.
@@ -1579,7 +1965,9 @@ static void qfillcmd()
    }
    printf("\n");
    weofcmd();
    }
    printf("\n");
    weofcmd();
-   weofcmd();
+   if (dev_cap(dev, CAP_TWOEOF)) {
+      weofcmd();
+   }
    rewindcmd();
    scan_blocks();
 
    rewindcmd();
    scan_blocks();
 
@@ -1602,15 +1990,19 @@ static void rawfill_cmd()
    uint32_t block_num = 0;
    uint32_t *p;
    int my_errno;
    uint32_t block_num = 0;
    uint32_t *p;
    int my_errno;
+   uint32_t i;
 
    block = new_block(dev);
    fd = open("/dev/urandom", O_RDONLY);
    if (fd) {
       read(fd, block->buf, block->buf_len);
 
    block = new_block(dev);
    fd = open("/dev/urandom", O_RDONLY);
    if (fd) {
       read(fd, block->buf, block->buf_len);
+      close(fd);
    } else {
    } else {
-      Pmsg0(0, "Cannot open /dev/urandom.\n");
-      free_block(block);
-      return;
+      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 raw blocks of %u bytes.\n", block->buf_len);
    }
    p = (uint32_t *)block->buf;
    Pmsg1(0, "Begin writing raw blocks of %u bytes.\n", block->buf_len);
@@ -1622,6 +2014,10 @@ static void rawfill_cmd()
             printf("+");
            fflush(stdout);
         }
             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];
+        }
         continue;
       }
       break;
         continue;
       }
       break;
@@ -1644,16 +2040,20 @@ static void bfill_cmd()
    uint32_t block_num = 0;
    uint32_t *p;
    int my_errno;
    uint32_t block_num = 0;
    uint32_t *p;
    int my_errno;
-   int fd;
+   int fd;   
+   uint32_t i;
 
    block = new_block(dev);
    fd = open("/dev/urandom", O_RDONLY);
    if (fd) {
       read(fd, block->buf, block->buf_len);
 
    block = new_block(dev);
    fd = open("/dev/urandom", O_RDONLY);
    if (fd) {
       read(fd, block->buf, block->buf_len);
+      close(fd);
    } else {
    } else {
-      Pmsg0(0, "Cannot open /dev/urandom.\n");
-      free_block(block);
-      return;
+      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);
    }
    p = (uint32_t *)block->buf;
    Pmsg1(0, "Begin writing Bacula blocks of %u bytes.\n", block->buf_len);
@@ -1668,6 +2068,10 @@ static void bfill_cmd()
          printf("+");
         fflush(stdout);
       }
          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");
    }
    my_errno = errno;
    printf("\n");
@@ -1679,6 +2083,7 @@ static void bfill_cmd()
 
 struct cmdstruct { char *key; void (*func)(); char *help; }; 
 static struct cmdstruct commands[] = {
 
 struct cmdstruct { char *key; void (*func)(); char *help; }; 
 static struct cmdstruct commands[] = {
+ {"autochanger", autochangercmd, "test autochanger"},
  {"bsf",        bsfcmd,       "backspace file"},
  {"bsr",        bsrcmd,       "backspace record"},
  {"bfill",      bfill_cmd,    "fill tape using Bacula writes"},
  {"bsf",        bsfcmd,       "backspace file"},
  {"bsr",        bsrcmd,       "backspace record"},
  {"bfill",      bfill_cmd,    "fill tape using Bacula writes"},
@@ -1713,15 +2118,16 @@ static void
 do_tape_cmds()
 {
    unsigned int i;
 do_tape_cmds()
 {
    unsigned int i;
-   int found;
+   bool found;
 
    while (get_cmd("*")) {
       sm_check(__FILE__, __LINE__, False);
 
    while (get_cmd("*")) {
       sm_check(__FILE__, __LINE__, False);
-      found = 0;
+      found = false;
+      parse_args(cmd, &args, &argc, argk, argv, MAX_CMD_ARGS);
       for (i=0; i<comsize; i++)       /* search for command */
       for (i=0; i<comsize; i++)       /* search for command */
-        if (fstrsch(cmd,  commands[i].key)) {
+        if (argc > 0 && fstrsch(argk[0],  commands[i].key)) {
            (*commands[i].func)();    /* go execute command */
            (*commands[i].func)();    /* go execute command */
-           found = 1;
+           found = true;
            break;
         }
       if (!found)
            break;
         }
       if (!found)
@@ -1735,6 +2141,7 @@ static void helpcmd()
 {
    unsigned int i;
    usage();
 {
    unsigned int i;
    usage();
+   printf(_("Interactive commands:\n"));
    printf(_("  Command    Description\n  =======    ===========\n"));
    for (i=0; i<comsize; i++)
       printf("  %-10s %s\n", commands[i].key, commands[i].help);
    printf(_("  Command    Description\n  =======    ===========\n"));
    for (i=0; i<comsize; i++)
       printf("  %-10s %s\n", commands[i].key, commands[i].help);
@@ -1745,9 +2152,9 @@ static void usage()
 {
    fprintf(stderr, _(
 "\nVersion: " VERSION " (" BDATE ")\n\n"
 {
    fprintf(stderr, _(
 "\nVersion: " VERSION " (" BDATE ")\n\n"
-"Usage: btape [-c config_file] [-d debug_level] [device_name]\n"
+"Usage: btape <options> <device_name>\n"
 "       -c <file>   set configuration file to file\n"
 "       -c <file>   set configuration file to file\n"
-"       -dnn        set debug level to nn\n"
+"       -d <nn>     set debug level to nn\n"
 "       -s          turn off signals\n"
 "       -t          open the default tape device\n"
 "       -?          print this message.\n"  
 "       -s          turn off signals\n"
 "       -t          open the default tape device\n"
 "       -?          print this message.\n"  
@@ -1790,7 +2197,7 @@ get_cmd(char *prompt)
 
 /* Dummies to replace askdir.c */
 int    dir_get_volume_info(JCR *jcr, enum get_vol_info_rw  writing) { return 1;}
 
 /* Dummies to replace askdir.c */
 int    dir_get_volume_info(JCR *jcr, enum get_vol_info_rw  writing) { return 1;}
-int    dir_update_volume_info(JCR *jcr, VOLUME_CAT_INFO *vol, int relabel) { return 1; }
+int    dir_update_volume_info(JCR *jcr, DEVICE *dev, int relabel) { return 1; }
 int    dir_create_jobmedia_record(JCR *jcr) { return 1; }
 int    dir_update_file_attributes(JCR *jcr, DEV_RECORD *rec) { return 1;}
 int    dir_send_job_status(JCR *jcr) {return 1;}
 int    dir_create_jobmedia_record(JCR *jcr) { return 1; }
 int    dir_update_file_attributes(JCR *jcr, DEV_RECORD *rec) { return 1;}
 int    dir_send_job_status(JCR *jcr) {return 1;}
@@ -1878,3 +2285,81 @@ static void set_volume_name(char *VolName, int volnum)
    pm_strcpy(&jcr->VolumeName, VolName);
    bstrncpy(dev->VolCatInfo.VolCatName, VolName, sizeof(dev->VolCatInfo.VolCatName));
 }
    pm_strcpy(&jcr->VolumeName, VolName);
    bstrncpy(dev->VolCatInfo.VolCatName, VolName, sizeof(dev->VolCatInfo.VolCatName));
 }
+
+/*
+ * Edit codes into ChangerCommand
+ *  %% = %
+ *  %a = archive device name
+ *  %c = changer device name
+ *  %f = Client's name
+ *  %j = Job name
+ *  %o = command
+ *  %s = Slot base 0
+ *  %S = Slot base 1
+ *  %v = Volume name
+ *
+ *
+ *  omsg = edited output message
+ *  imsg = input string containing edit codes (%x)
+ *  cmd = command string (load, unload, ...) 
+ *
+ */
+static char *edit_device_codes(JCR *jcr, char *omsg, char *imsg, char *cmd) 
+{
+   char *p;
+   const char *str;
+   char add[20];
+
+   *omsg = 0;
+   Dmsg1(400, "edit_device_codes: %s\n", imsg);
+   for (p=imsg; *p; p++) {
+      if (*p == '%') {
+        switch (*++p) {
+         case '%':
+            str = "%";
+           break;
+         case 'a':
+           str = dev_name(jcr->device->dev);
+           break;
+         case 'c':
+           str = NPRT(jcr->device->changer_name);
+           break;
+         case 'o':
+           str = NPRT(cmd);
+           break;
+         case 's':
+            sprintf(add, "%d", jcr->VolCatInfo.Slot - 1);
+           str = add;
+           break;
+         case 'S':
+            sprintf(add, "%d", jcr->VolCatInfo.Slot);
+           str = add;
+           break;
+         case 'j':                    /* Job name */
+           str = jcr->Job;
+           break;
+         case 'v':
+           str = NPRT(jcr->VolumeName);
+           break;
+         case 'f':
+           str = NPRT(jcr->client_name);
+           break;
+
+        default:
+            add[0] = '%';
+           add[1] = *p;
+           add[2] = 0;
+           str = add;
+           break;
+        }
+      } else {
+        add[0] = *p;
+        add[1] = 0;
+        str = add;
+      }
+      Dmsg1(400, "add_str %s\n", str);
+      pm_strcat(&omsg, (char *)str);
+      Dmsg1(400, "omsg=%s\n", omsg);
+   }
+   return omsg;
+}