]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/stored/mount.c
Backport from Bacula Enterprise
[bacula/bacula] / bacula / src / stored / mount.c
index 6175d8bc2611a8600de8da755da00ba3568e9a26..0aae19a5dc4e341615a7f4c1bc8efc7f6c29a7d1 100644 (file)
@@ -1,3 +1,22 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2015 Kern Sibbald
+   Copyright (C) 2002-2015 Free Software Foundation Europe e.V.
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is 
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
 /*
  *
  *  Routines for handling mounting tapes for reading and for
  *
  *   Kern Sibbald, August MMII
  *
- *   Version $Id$
  */
-/*
-   Bacula® - The Network Backup Solution
-
-   Copyright (C) 2002-2007 Free Software Foundation Europe e.V.
-
-   The main author of Bacula is Kern Sibbald, with contributions from
-   many others, a complete list can be found in the file AUTHORS.
-   This program is Free Software; you can redistribute it and/or
-   modify it under the terms of version two of the GNU General Public
-   License as published by the Free Software Foundation plus additions
-   that are listed 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 General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301, USA.
-
-   Bacula® is a registered trademark of John Walker.
-   The licensor of Bacula is the Free Software Foundation Europe
-   (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
-   Switzerland, email:ftf@fsfeurope.org.
-*/
 
 #include "bacula.h"                   /* pull in global headers */
 #include "stored.h"                   /* pull in Storage Deamon headers */
 
-static void mark_volume_not_inchanger(DCR *dcr);
-static int try_autolabel(DCR *dcr, bool opened);
+static pthread_mutex_t mount_mutex = PTHREAD_MUTEX_INITIALIZER;
 
 enum {
    try_next_vol = 1,
@@ -48,6 +38,13 @@ enum {
    try_default
 };
 
+enum {
+   check_next_vol = 1,
+   check_ok,
+   check_read_vol,
+   check_error
+};
+
 /*
  * If release is set, we rewind the current volume,
  * which we no longer want, and ask the user (console)
@@ -59,68 +56,70 @@ enum {
  * This routine returns a 0 only if it is REALLY
  *  impossible to get the requested Volume.
  *
+ * This routine is entered with the device blocked, but not
+ *   locked.
+ *
  */
-bool mount_next_write_volume(DCR *dcr, bool release)
+bool DCR::mount_next_write_volume()
 {
    int retry = 0;
    bool ask = false, recycle, autochanger;
-   int vol_label_status;
-   DEVICE *dev = dcr->dev;
-   JCR *jcr = dcr->jcr;
-   DEV_BLOCK *block = dcr->block;
    int mode;
+   DCR *dcr = this;
 
-   Dmsg2(150, "Enter mount_next_volume(release=%d) dev=%s\n", release,
+   Enter(200);
+   Dmsg2(100, "Enter mount_next_volume(release=%d) dev=%s\n", dev->must_unload(),
       dev->print_name());
 
    init_device_wait_timers(dcr);
 
+   P(mount_mutex);
+
    /*
     * Attempt to mount the next volume. If something non-fatal goes
     *  wrong, we come back here to re-try (new op messages, re-read
     *  Volume, ...)
     */
 mount_next_vol:
-   Dmsg1(150, "mount_next_vol retry=%d\n", retry);
+   Dmsg1(100, "mount_next_vol retry=%d\n", retry);
    /* Ignore retry if this is poll request */
-   if (!dev->poll && retry++ > 4) {
+   if (dev->is_nospace() || retry++ > 4) {
       /* Last ditch effort before giving up, force operator to respond */
-      dcr->VolCatInfo.Slot = 0;
-      if (!dir_ask_sysop_to_mount_volume(dcr)) {
-         Jmsg(jcr, M_FATAL, 0, _("Too many errors trying to mount device %s.\n"),
-              dev->print_name());
-         return false;
+      VolCatInfo.Slot = 0;
+      V(mount_mutex);
+      if (!dir_ask_sysop_to_mount_volume(dcr, SD_APPEND)) {
+         Jmsg(jcr, M_FATAL, 0, _("Too many errors trying to mount %s device %s.\n"),
+              dev->print_type(), dev->print_name());
+         goto no_lock_bail_out;
       }
+      P(mount_mutex);
+      Dmsg1(90, "Continue after dir_ask_sysop_to_mount. must_load=%d\n", dev->must_load());
    }
    if (job_canceled(jcr)) {
       Jmsg(jcr, M_FATAL, 0, _("Job %d canceled.\n"), jcr->JobId);
-      return false;
+      goto bail_out;
    }
    recycle = false;
-   if (release) {
-      Dmsg0(150, "mount_next_volume release=1\n");
-      release_volume(dcr);
+
+   if (dev->must_unload()) {
       ask = true;                     /* ask operator to mount tape */
    }
+   do_unload();
+   do_swapping(SD_APPEND);
+   do_load(SD_APPEND);
 
-   /*
-    * Get Director's idea of what tape we should have mounted.
-    *    in dcr->VolCatInfo
-    */
-   Dmsg0(200, "Before dir_find_next_appendable_volume.\n");
-   while (!dir_find_next_appendable_volume(dcr)) {
-       Dmsg0(200, "not dir_find_next\n");
-       if (!dir_ask_sysop_to_create_appendable_volume(dcr)) {
-         return false;
-       }
-       Dmsg0(200, "Again dir_find_next_append...\n");
+   if (!find_a_volume()) {
+      goto bail_out;
    }
+
    if (job_canceled(jcr)) {
-      return false;
+      goto bail_out;
    }
-   Dmsg3(150, "After find_next_append. Vol=%s Slot=%d Parts=%d\n",
-         dcr->VolCatInfo.VolCatName, dcr->VolCatInfo.Slot, dcr->VolCatInfo.VolCatParts);
-   
+   Dmsg3(100, "After find_a_volume. Vol=%s Slot=%d VolType=%d\n",
+         getVolCatName(), VolCatInfo.Slot, VolCatInfo.VolCatType);
+
+   dev->notify_newvol_in_attached_dcrs(getVolCatName());
+
    /*
     * Get next volume and ready it for append
     * This code ensures that the device is ready for
@@ -132,43 +131,55 @@ mount_next_vol:
     * and move the tape to the end of data.
     *
     */
-   if (autoload_device(dcr, 1, NULL) > 0) {
+   dcr->setVolCatInfo(false);   /* out of date when Vols unlocked */
+   if (autoload_device(dcr, SD_APPEND, NULL) > 0) {
       autochanger = true;
       ask = false;
    } else {
       autochanger = false;
-      dcr->VolCatInfo.Slot = 0;
+      VolCatInfo.Slot = 0;
+      if (dev->is_autochanger() && !VolCatInfo.InChanger) {
+         ask = true;      /* not in changer, do not retry */
+      } else {
+         ask = retry >= 2;
+      }
    }
-   Dmsg1(200, "autoload_dev returns %d\n", autochanger);
+   Dmsg1(100, "autoload_dev returns %d\n", autochanger);
    /*
     * If we autochanged to correct Volume or (we have not just
     *   released the Volume AND we can automount) we go ahead
     *   and read the label. If there is no tape in the drive,
-    *   we will err, recurse and ask the operator the next time.
+    *   we will fail, recurse and ask the operator the next time.
     */
-   if (!release && dev->is_tape() && dev->has_cap(CAP_AUTOMOUNT)) {
-      Dmsg0(150, "(1)Ask=0\n");
+   if (!dev->must_unload() && dev->is_tape() && dev->has_cap(CAP_AUTOMOUNT)) {
+      Dmsg0(250, "(1)Ask=0\n");
       ask = false;                 /* don't ask SYSOP this time */
    }
    /* Don't ask if not removable */
    if (!dev->is_removable()) {
-      Dmsg0(150, "(2)Ask=0\n");
+      Dmsg0(250, "(2)Ask=0\n");
       ask = false;
    }
-   Dmsg2(150, "Ask=%d autochanger=%d\n", ask, autochanger);
-   release = true;                /* release next time if we "recurse" */
-
-   if (ask && !dir_ask_sysop_to_mount_volume(dcr)) {
-      Dmsg0(150, "Error return ask_sysop ...\n");
-      return false;          /* error return */
+   Dmsg2(100, "Ask=%d autochanger=%d\n", ask, autochanger);
+
+   if (ask) {
+      V(mount_mutex);
+      dcr->setVolCatInfo(false);   /* out of date when Vols unlocked */
+      if (!dir_ask_sysop_to_mount_volume(dcr, SD_APPEND)) {
+         Dmsg0(150, "Error return ask_sysop ...\n");
+         goto no_lock_bail_out;
+      }
+      P(mount_mutex);
    }
    if (job_canceled(jcr)) {
-      return false;
+      goto bail_out;
    }
-   Dmsg1(150, "want vol=%s\n", dcr->VolumeName);
+   Dmsg3(100, "want vol=%s devvol=%s dev=%s\n", VolumeName,
+      dev->VolHdr.VolumeName, dev->print_name());
 
    if (dev->poll && dev->has_cap(CAP_CLOSEONPOLL)) {
       dev->close();
+      free_volume(dev);
    }
 
    /* Ensure the device is open */
@@ -178,11 +189,13 @@ mount_next_vol:
       mode = OPEN_READ_WRITE;
    }
    /* Try autolabel if enabled */
-   if (dev->open(dcr, mode) < 0) {
-      try_autolabel(dcr, false);      /* try to create a new volume label */
+   Dmsg1(100, "Try open Vol=%s\n", getVolCatName());
+   if (!dev->open(dcr, mode)) {
+      Dmsg1(100, "Try autolabel Vol=%s\n", getVolCatName());
+      try_autolabel(false);      /* try to create a new volume label */
    }
-   while (dev->open(dcr, mode) < 0) {
-      Dmsg1(150, "open_device failed: ERR=%s\n", dev->bstrerror());
+   while (!dev->open(dcr, mode)) {
+      Dmsg1(100, "open_device failed: ERR=%s\n", dev->bstrerror());
       if ((dev->is_file() && dev->is_removable()) || dev->is_dvd()) {
          bool ok = true;
          Dmsg0(150, "call scan_dir_for_vol\n");
@@ -192,7 +205,7 @@ mount_next_vol:
             }
          }
          if (ok && dev->scan_dir_for_volume(dcr)) {
-            if (dev->open(dcr, mode) >= 0) {
+            if (dev->open(dcr, mode)) {
                break;                    /* got a valid volume */
             }
          }
@@ -200,113 +213,296 @@ mount_next_vol:
             dev->unmount(0);
          }
       }
-      if (try_autolabel(dcr, false) == try_read_vol) {
+      if (try_autolabel(false) == try_read_vol) {
          break;                       /* created a new volume label */
       }
-      /* If DVD, ignore the error, very often you cannot open the device
-       * (when there is no DVD, or when the one inserted is a wrong one) */
-      if (dev->poll || dev->is_dvd() || dev->is_removable()) {
-         goto mount_next_vol;
+
+      Jmsg4(jcr, M_WARNING, 0, _("Open of %s device %s Volume \"%s\" failed: ERR=%s\n"),
+            dev->print_type(), dev->print_name(), dcr->VolumeName, dev->bstrerror());
+
+      /* If not removable, Volume is broken. This is a serious issue here. */
+      if(dev->is_file() && !dev->is_removable()) {
+         Dmsg3(40, "Volume \"%s\" not loaded on %s device %s.\n",
+               dcr->VolumeName, dev->print_type(), dev->print_name());
+         mark_volume_in_error();
+
       } else {
-         Jmsg(jcr, M_ERROR, 0, _("Could not open device %s: ERR=%s\n"),
-            dev->print_name(), dev->print_errmsg());
-         return false;
+         Dmsg0(100, "set_unload\n");
+         dev->set_unload();              /* force ask sysop */
+         ask = true;
       }
+
+      Dmsg0(100, "goto mount_next_vol\n");
+      goto mount_next_vol;
    }
 
    /*
-    * Now make sure we have the right tape mounted
+    * Now check the volume label to make sure we have the right tape mounted
     */
 read_volume:
+   switch (check_volume_label(ask, autochanger)) {
+   case check_next_vol:
+      Dmsg0(50, "set_unload\n");
+      dev->set_unload();                 /* want a different Volume */
+      Dmsg0(100, "goto mount_next_vol\n");
+      goto mount_next_vol;
+   case check_read_vol:
+      goto read_volume;
+   case check_error:
+      goto bail_out;
+   case check_ok:
+      break;
+   }
+   /*
+    * Check that volcatinfo is good
+    */
+   if (!dev->haveVolCatInfo()) {
+      Dmsg0(100, "Do not have volcatinfo\n");
+      if (!find_a_volume()) {
+         goto mount_next_vol;
+      }
+      dev->VolCatInfo = VolCatInfo;      /* structure assignment */
+   }
+
+   /*
+    * See if we have a fresh tape or a tape with data.
+    *
+    * Note, if the LabelType is PRE_LABEL, it was labeled
+    *  but never written. If so, rewrite the label but set as
+    *  VOL_LABEL.  We rewind and return the label (reconstructed)
+    *  in the block so that in the case of a new tape, data can
+    *  be appended just after the block label.  If we are writing
+    *  a second volume, the calling routine will write the label
+    *  before writing the overflow block.
+    *
+    *  If the tape is marked as Recycle, we rewrite the label.
+    */
+   recycle = strcmp(dev->VolCatInfo.VolCatStatus, "Recycle") == 0;
+   if (dev->VolHdr.LabelType == PRE_LABEL || recycle) {
+      dcr->WroteVol = false;
+      if (!dcr->rewrite_volume_label(recycle)) {
+         mark_volume_in_error();
+         goto mount_next_vol;
+      }
+   } else {
+      /*
+       * OK, at this point, we have a valid Bacula label, but
+       * we need to position to the end of the volume, since we are
+       * just now putting it into append mode.
+       */
+      Dmsg1(100, "Device previously written, moving to end of data. Expect %lld bytes\n",
+           dev->VolCatInfo.VolCatBytes);
+      Jmsg(jcr, M_INFO, 0, _("Volume \"%s\" previously written, moving to end of data.\n"),
+         VolumeName);
+
+      if (!dev->eod(dcr)) {
+         Dmsg3(050, "Unable to position to end of data on %s device %s: ERR=%s\n",
+            dev->print_type(), dev->print_name(), dev->bstrerror());
+         Jmsg(jcr, M_ERROR, 0, _("Unable to position to end of data on %s device %s: ERR=%s\n"),
+            dev->print_type(), dev->print_name(), dev->bstrerror());
+         mark_volume_in_error();
+         goto mount_next_vol;
+      }
+
+      if (!is_eod_valid()) {
+         Dmsg0(100, "goto mount_next_vol\n");
+         goto mount_next_vol;
+      }
+
+      dev->VolCatInfo.VolCatMounts++;      /* Update mounts */
+      Dmsg1(150, "update volinfo mounts=%d\n", dev->VolCatInfo.VolCatMounts);
+      if (!dir_update_volume_info(dcr, false, false)) {
+         goto bail_out;
+      }
+
+      /* Return an empty block */
+      empty_block(block);             /* we used it for reading so set for write */
+   }
+   dev->set_append();
+   Dmsg1(150, "set APPEND, normal return from mount_next_write_volume. dev=%s\n",
+      dev->print_name());
+
+   V(mount_mutex);
+   return true;
+
+bail_out:
+   V(mount_mutex);
+
+no_lock_bail_out:
+   Leave(200);
+   return false;
+}
+
+/*
+ * This routine is meant to be called once the first pass
+ *   to ensure that we have a candidate volume to mount.
+ *   Otherwise, we ask the sysop to created one.
+ * Note, mount_mutex is already locked on entry and thus
+ *   must remain locked on exit from this function.
+ */
+bool DCR::find_a_volume()
+{
+   DCR *dcr = this;
+   bool ok;
+
+   if (!is_suitable_volume_mounted()) {
+      bool have_vol = false;
+      /* Do we have a candidate volume? */
+      if (dev->vol) {
+         bstrncpy(VolumeName, dev->vol->vol_name, sizeof(VolumeName));
+         have_vol = dir_get_volume_info(this, GET_VOL_INFO_FOR_WRITE);
+      }
+      /*
+       * Get Director's idea of what tape we should have mounted.
+       *    in dcr->VolCatInfo
+       */
+      if (!have_vol) {
+         Dmsg0(200, "Before dir_find_next_appendable_volume.\n");
+         while (!dir_find_next_appendable_volume(dcr)) {
+            Dmsg0(200, "not dir_find_next\n");
+            if (job_canceled(jcr)) {
+               return false;
+            }
+            /*
+             * Unlock the mount mutex while waiting or
+             *   the Director for a new volume
+             */
+            V(mount_mutex);
+            if (dev->must_wait()) {
+               int retries = 5;
+               Dmsg0(40, "No appendable volume. Calling wait_for_device\n");
+               wait_for_device(dcr, retries);
+               ok = true;
+            } else {
+               ok = dir_ask_sysop_to_create_appendable_volume(dcr);
+            }
+            P(mount_mutex);
+            if (!ok || job_canceled(jcr)) {
+               return false;
+            }
+            Dmsg0(150, "Again dir_find_next_append...\n");
+         }
+         dev->clear_wait();
+      }
+   }
+   if (dcr->haveVolCatInfo()) {
+      return true;
+   }
+   return dir_get_volume_info(dcr, GET_VOL_INFO_FOR_WRITE);
+}
+
+int DCR::check_volume_label(bool &ask, bool &autochanger)
+{
+   int vol_label_status;
+
+   Enter(200);
+
    /*
     * If we are writing to a stream device, ASSUME the volume label
     *  is correct.
     */
    if (dev->has_cap(CAP_STREAM)) {
       vol_label_status = VOL_OK;
-      create_volume_label(dev, dcr->VolumeName, "Default", false /* not DVD */);
+      create_volume_header(dev, VolumeName, "Default", false /* not DVD */);
       dev->VolHdr.LabelType = PRE_LABEL;
    } else {
-      vol_label_status = read_dev_volume_label(dcr);
+      vol_label_status = read_dev_volume_label(this);
    }
    if (job_canceled(jcr)) {
-      return false;
+      goto check_bail_out;
    }
 
-   Dmsg2(150, "Want dirVol=%s dirStat=%s\n", dcr->VolumeName,
-      dcr->VolCatInfo.VolCatStatus);
+   Dmsg2(150, "Want dirVol=%s dirStat=%s\n", VolumeName,
+      VolCatInfo.VolCatStatus);
+
    /*
     * At this point, dev->VolCatInfo has what is in the drive, if anything,
     *          and   dcr->VolCatInfo has what the Director wants.
     */
    switch (vol_label_status) {
    case VOL_OK:
-      Dmsg1(150, "Vol OK name=%s\n", dcr->VolumeName);
-      dev->VolCatInfo = dcr->VolCatInfo;       /* structure assignment */
-      recycle = strcmp(dev->VolCatInfo.VolCatStatus, "Recycle") == 0;
+      Dmsg1(150, "Vol OK name=%s\n", dev->VolHdr.VolumeName);
+      dev->VolCatInfo = VolCatInfo;       /* structure assignment */
       break;                    /* got a Volume */
    case VOL_NAME_ERROR:
       VOLUME_CAT_INFO dcrVolCatInfo, devVolCatInfo;
-      char VolumeName[MAX_NAME_LENGTH];
+      char saveVolumeName[MAX_NAME_LENGTH];
+
+      Dmsg2(40, "Vol NAME Error Have=%s, want=%s\n", dev->VolHdr.VolumeName, VolumeName);
+      if (dev->is_volume_to_unload()) {
+         ask = true;
+         goto check_next_volume;
+      }
 
+#ifdef xxx
       /* If not removable, Volume is broken */
       if (!dev->is_removable()) {
-         Jmsg(jcr, M_WARNING, 0, _("Volume \"%s\" not on device %s.\n"),
-            dcr->VolumeName, dev->print_name());
-         mark_volume_in_error(dcr);
-         goto mount_next_vol;
+         Jmsg3(jcr, M_WARNING, 0, _("Volume \"%s\" not loaded on %s device %s.\n"),
+            VolumeName, dev->print_type(), dev->print_name());
+         Dmsg3(40, "Volume \"%s\" not loaded on %s device %s.\n",
+            VolumeName, dev->print_type(), dev->print_name());
+         mark_volume_in_error();
+         goto check_next_volume;
       }
+#endif
 
-      Dmsg1(150, "Vol NAME Error Name=%s\n", dcr->VolumeName);
-      /* If polling and got a previous bad name, ignore it */
-      if (dev->poll && strcmp(dev->BadVolName, dev->VolHdr.VolumeName) == 0) {
-         ask = true;
-         Dmsg1(200, "Vol Name error supress due to poll. Name=%s\n", dcr->VolumeName);
-         goto mount_next_vol;
-      }
       /*
        * OK, we got a different volume mounted. First save the
        *  requested Volume info (dcr) structure, then query if
        *  this volume is really OK. If not, put back the desired
        *  volume name, mark it not in changer and continue.
        */
-      dcrVolCatInfo = dcr->VolCatInfo;      /* structure assignment */
+      dcrVolCatInfo = VolCatInfo;      /* structure assignment */
       devVolCatInfo = dev->VolCatInfo;      /* structure assignment */
       /* Check if this is a valid Volume in the pool */
-      bstrncpy(VolumeName, dcr->VolumeName, sizeof(VolumeName));
-      bstrncpy(dcr->VolumeName, dev->VolHdr.VolumeName, sizeof(dcr->VolumeName));
-      if (!dir_get_volume_info(dcr, GET_VOL_INFO_FOR_WRITE)) {
+      bstrncpy(saveVolumeName, VolumeName, sizeof(saveVolumeName));
+      bstrncpy(VolumeName, dev->VolHdr.VolumeName, sizeof(VolumeName));
+      if (!dir_get_volume_info(this, GET_VOL_INFO_FOR_WRITE)) {
+         POOL_MEM vol_info_msg;
+         pm_strcpy(vol_info_msg, jcr->dir_bsock->msg);  /* save error message */
          /* Restore desired volume name, note device info out of sync */
          /* This gets the info regardless of the Pool */
-         bstrncpy(dcr->VolumeName, dev->VolHdr.VolumeName, sizeof(dcr->VolumeName));
-         if (autochanger && !dir_get_volume_info(dcr, GET_VOL_INFO_FOR_READ)) {
+         bstrncpy(VolumeName, dev->VolHdr.VolumeName, sizeof(VolumeName));
+         if (autochanger && !dir_get_volume_info(this, GET_VOL_INFO_FOR_READ)) {
             /*
              * If we get here, we know we cannot write on the Volume,
-             *  and we know that we cannot read it either, so it 
+             *  and we know that we cannot read it either, so it
              *  is not in the autochanger.
              */
-            mark_volume_not_inchanger(dcr);
+            mark_volume_not_inchanger();
          }
          dev->VolCatInfo = devVolCatInfo;    /* structure assignment */
-         bstrncpy(dev->BadVolName, dev->VolHdr.VolumeName, sizeof(dev->BadVolName));
+         dev->set_unload();                  /* unload this volume */
          Jmsg(jcr, M_WARNING, 0, _("Director wanted Volume \"%s\".\n"
               "    Current Volume \"%s\" not acceptable because:\n"
               "    %s"),
              dcrVolCatInfo.VolCatName, dev->VolHdr.VolumeName,
-             jcr->dir_bsock->msg);
+             vol_info_msg.c_str());
          ask = true;
          /* Restore saved DCR before continuing */
-         bstrncpy(dcr->VolumeName, VolumeName, sizeof(dcr->VolumeName));
-         dcr->VolCatInfo = dcrVolCatInfo;  /* structure assignment */
-         goto mount_next_vol;
+         bstrncpy(VolumeName, saveVolumeName, sizeof(VolumeName));
+         VolCatInfo = dcrVolCatInfo;  /* structure assignment */
+         goto check_next_volume;
       }
       /*
        * This was not the volume we expected, but it is OK with
        * the Director, so use it.
        */
-      Dmsg1(150, "want new name=%s\n", dcr->VolumeName);
-      dev->VolCatInfo = dcr->VolCatInfo;   /* structure assignment */
-      recycle = strcmp(dev->VolCatInfo.VolCatStatus, "Recycle") == 0;
+      Dmsg1(150, "Got new Volume name=%s\n", VolumeName);
+      dev->VolCatInfo = VolCatInfo;   /* structure assignment */
+      Dmsg1(100, "Call reserve_volume=%s\n", dev->VolHdr.VolumeName);
+      if (reserve_volume(this, dev->VolHdr.VolumeName) == NULL) {
+         if (!jcr->errmsg[0]) {
+            Jmsg3(jcr, M_WARNING, 0, _("Could not reserve volume %s on %s device %s\n"),
+               dev->VolHdr.VolumeName, dev->print_type(), dev->print_name());
+         } else {
+            Jmsg(jcr, M_WARNING, 0, "%s", jcr->errmsg);
+         }
+         ask = true;
+         dev->setVolCatInfo(false);
+         setVolCatInfo(false);
+         goto check_next_volume;
+      }
       break;                /* got a Volume */
    /*
     * At this point, we assume we have a blank tape mounted.
@@ -314,22 +510,21 @@ read_volume:
    case VOL_IO_ERROR:
       if (dev->is_dvd()) {
          Jmsg(jcr, M_FATAL, 0, "%s", jcr->errmsg);
-         mark_volume_in_error(dcr);
-         return false;       /* we could not write on DVD */
+         mark_volume_in_error();
+         goto check_bail_out;       /* we could not write on DVD */
       }
       /* Fall through wanted */
    case VOL_NO_LABEL:
-      switch (try_autolabel(dcr, true)) {
+      switch (try_autolabel(true)) {
       case try_next_vol:
-         goto mount_next_vol;
+         goto check_next_volume;
       case try_read_vol:
-         goto read_volume;
+         goto check_read_volume;
       case try_error:
-         return false;
+         goto check_bail_out;
       case try_default:
          break;
       }
-
       /* NOTE! Fall-through wanted. */
    case VOL_NO_MEDIA:
    default:
@@ -343,121 +538,202 @@ read_volume:
       /* Needed, so the medium can be changed */
       if (dev->requires_mount()) {
          dev->close();
+         free_volume(dev);
       }
-      goto mount_next_vol;
+      goto check_next_volume;
+   }
+   Leave(200);
+   return check_ok;
+
+check_next_volume:
+   dev->setVolCatInfo(false);
+   setVolCatInfo(false);
+   Leave(200);
+   return check_next_vol;
+
+check_bail_out:
+   Leave(200);
+   return check_error;
+
+check_read_volume:
+   Leave(200);
+   return check_read_vol;
+
+}
+
+
+bool DCR::is_suitable_volume_mounted()
+{
+   bool ok;
+
+   /* Volume mounted? */
+   if (dev->VolHdr.VolumeName[0] == 0 || dev->swap_dev || dev->must_unload()) {
+      return false;                      /* no */
+   }
+   bstrncpy(VolumeName, dev->VolHdr.VolumeName, sizeof(VolumeName));
+   ok = dir_get_volume_info(this, GET_VOL_INFO_FOR_WRITE);
+   if (!ok) {
+      Dmsg1(40, "dir_get_volume_info failed: %s", jcr->errmsg);
+      dev->set_wait();
+   }
+   return ok;
+}
+
+bool DCR::do_unload()
+{
+   if (dev->must_unload()) {
+      Dmsg1(100, "must_unload release %s\n", dev->print_name());
+      release_volume();
    }
+   return false;
+}
 
+bool DCR::do_load(bool is_writing)
+{
+   if (dev->must_load()) {
+      Dmsg1(100, "Must load dev=%s\n", dev->print_name());
+      if (autoload_device(this, is_writing, NULL) > 0) {
+         dev->clear_load();
+         return true;
+      }
+      return false;
+   }
+   return true;
+}
+
+void DCR::do_swapping(bool is_writing)
+{
    /*
-    * See if we have a fresh tape or a tape with data.
-    *
-    * Note, if the LabelType is PRE_LABEL, it was labeled
-    *  but never written. If so, rewrite the label but set as
-    *  VOL_LABEL.  We rewind and return the label (reconstructed)
-    *  in the block so that in the case of a new tape, data can
-    *  be appended just after the block label.  If we are writing
-    *  a second volume, the calling routine will write the label
-    *  before writing the overflow block.
-    *
-    *  If the tape is marked as Recycle, we rewrite the label.
+    * See if we are asked to swap the Volume from another device
+    *  if so, unload the other device here, and attach the
+    *  volume to our drive.
     */
-   if (dev->VolHdr.LabelType == PRE_LABEL || recycle) {
-      if (!rewrite_volume_label(dcr, recycle)) {
-         mark_volume_in_error(dcr);
-         goto mount_next_vol;
+   if (dev->swap_dev) {
+      if (dev->swap_dev->must_unload()) {
+         if (dev->vol) {
+            dev->swap_dev->set_slot(dev->vol->get_slot());
+         }
+         Dmsg2(100, "Swap unloading slot=%d %s\n", dev->swap_dev->get_slot(),
+               dev->swap_dev->print_name());
+         unload_dev(this, dev->swap_dev);
       }
-   } else {
-      /*
-       * OK, at this point, we have a valid Bacula label, but
-       * we need to position to the end of the volume, since we are
-       * just now putting it into append mode.
-       */
-      Dmsg0(200, "Device previously written, moving to end of data\n");
-      Jmsg(jcr, M_INFO, 0, _("Volume \"%s\" previously written, moving to end of data.\n"),
-         dcr->VolumeName);
-      if (!dev->eod(dcr)) {
-         Jmsg(jcr, M_ERROR, 0, _("Unable to position to end of data on device %s: ERR=%s\n"),
-            dev->print_name(), dev->bstrerror());
-         mark_volume_in_error(dcr);
-         goto mount_next_vol;
+      if (dev->vol) {
+         dev->vol->clear_swapping();
+         Dmsg1(100, "=== set in_use vol=%s\n", dev->vol->vol_name);
+         dev->vol->set_in_use();
+         dev->VolHdr.VolumeName[0] = 0;  /* don't yet have right Volume */
+      } else {
+         Dmsg1(100, "No vol on dev=%s\n", dev->print_name());
       }
-      if (dev->is_dvd()) {
-         char ed1[50], ed2[50];
-         if (dev->VolCatInfo.VolCatBytes == dev->part_start + dev->part_size) {
-            Jmsg(jcr, M_INFO, 0, _("Ready to append to end of Volume \"%s\""
-                 " part=%d size=%s\n"), dcr->VolumeName, 
-                 dev->part, edit_uint64(dev->VolCatInfo.VolCatBytes,ed1));
-         } else {
-            Jmsg(jcr, M_ERROR, 0, _("I cannot write on Volume \"%s\" because: "
-                 "The sizes do not match! Volume=%s Catalog=%s\n"),
-                 dcr->VolumeName,
-                 edit_uint64(dev->part_start + dev->part_size, ed1),
-                 edit_uint64(dev->VolCatInfo.VolCatBytes, ed2));
-            mark_volume_in_error(dcr);
-            goto mount_next_vol;
-         }
+      if (dev->swap_dev->vol) {
+         Dmsg2(100, "Vol=%s on dev=%s\n", dev->swap_dev->vol->vol_name,
+              dev->swap_dev->print_name());
       }
-      /* *****FIXME**** we should do some checking for files too */
-      if (dev->is_tape()) {
-         /*
-          * Check if we are positioned on the tape at the same place
-          * that the database says we should be.
-          */
-         if (dev->VolCatInfo.VolCatFiles == dev->get_file()) {
-            Jmsg(jcr, M_INFO, 0, _("Ready to append to end of Volume \"%s\" at file=%d.\n"),
-                 dcr->VolumeName, dev->get_file());
-         } else {
-            Jmsg(jcr, M_ERROR, 0, _("I cannot write on Volume \"%s\" because:\n"
-                 "The number of files mismatch! Volume=%u Catalog=%u\n"),
-                 dcr->VolumeName, dev->get_file(), dev->VolCatInfo.VolCatFiles);
-            mark_volume_in_error(dcr);
-            goto mount_next_vol;
-         }
+      Dmsg2(100, "Set swap_dev=NULL for dev=%s swap_dev=%s\n",
+         dev->print_name(), dev->swap_dev->print_name());
+      dev->swap_dev = NULL;
+   } else {
+      if (dev->vol) {
+      Dmsg1(100, "No swap_dev set. dev->vol=%p\n", dev->vol);
+      } else {
+      Dmsg1(100, "No swap_dev set. dev->vol=%p\n", dev->vol);
       }
-      dev->VolCatInfo.VolCatMounts++;      /* Update mounts */
-      Dmsg1(150, "update volinfo mounts=%d\n", dev->VolCatInfo.VolCatMounts);
-      if (!dir_update_volume_info(dcr, false)) {
+   }
+}
+
+
+/*
+ * Check if the current position on the volume corresponds to
+ *  what is in the catalog.
+ */
+bool DCR::is_eod_valid()
+{
+   if (dev->is_dvd()) {
+      char ed1[50], ed2[50];
+      if (dev->VolCatInfo.VolCatBytes == dev->part_start + dev->part_size) {
+         Jmsg(jcr, M_INFO, 0, _("Ready to append to end of Volume \"%s\""
+              " part=%d size=%s\n"), VolumeName,
+              dev->part, edit_uint64_with_commas(dev->VolCatInfo.VolCatBytes,ed1));
+      } else {
+         Jmsg(jcr, M_ERROR, 0, _("Bacula cannot write on DVD Volume \"%s\" because: "
+              "The sizes do not match! Volume=%s Catalog=%s\n"),
+              VolumeName,
+              edit_uint64_with_commas(dev->part_start + dev->part_size, ed1),
+              edit_uint64_with_commas(dev->VolCatInfo.VolCatBytes, ed2));
+         mark_volume_in_error();
          return false;
       }
-      
+   } else if (dev->is_tape()) {
       /*
-       * DVD : check if the last part was removed or truncated, or if a written
-       * part was overwritten.   
-       * We need to do it after dir_update_volume_info, so we have the EndBlock
-       * info. (nb: I don't understand why VolCatFiles is set (used to check
-       * tape file number), but not EndBlock)
-       * Maybe could it be changed "dev->is_file()" (would remove the fixme above)   
-       *
-       * Disabled: I had problems with this code... 
-       * (maybe is it related to the seek bug ?)   
+       * Check if we are positioned on the tape at the same place
+       * that the database says we should be.
        */
-#ifdef xxx
-      if (dev->is_dvd()) {
-         Dmsg2(150, "DVD/File sanity check addr=%u vs endblock=%u\n", (unsigned int)dev->file_addr, (unsigned int)dev->VolCatInfo.EndBlock);
-         if (dev->file_addr == dev->VolCatInfo.EndBlock+1) {
-            Jmsg(jcr, M_INFO, 0, _("Ready to append to end of Volume \"%s\" at file address=%u.\n"),
-                 dcr->VolumeName, (unsigned int)dev->file_addr);
+      if (dev->VolCatInfo.VolCatFiles == dev->get_file()) {
+         Jmsg(jcr, M_INFO, 0, _("Ready to append to end of Volume \"%s\" at file=%d.\n"),
+              VolumeName, dev->get_file());
+      } else if (dev->get_file() > dev->VolCatInfo.VolCatFiles) {
+         Jmsg(jcr, M_WARNING, 0, _("For Volume \"%s\":\n"
+              "The number of files mismatch! Volume=%u Catalog=%u\n"
+              "Correcting Catalog\n"),
+              VolumeName, dev->get_file(), dev->VolCatInfo.VolCatFiles);
+         dev->VolCatInfo.VolCatFiles = dev->get_file();
+         dev->VolCatInfo.VolCatBlocks = dev->get_block_num();
+         if (!dir_update_volume_info(this, false, true)) {
+            Jmsg(jcr, M_WARNING, 0, _("Error updating Catalog\n"));
+            mark_volume_in_error();
+            return false;
          }
-         else {
-            Jmsg(jcr, M_ERROR, 0, _("I cannot write on Volume \"%s\" because:\n"
-                                    "The EOD file address is wrong: Volume file address=%u != Catalog Endblock=%u(+1)\n"
-                                    "You probably removed DVD last part in spool directory.\n"),
-                 dcr->VolumeName, (unsigned int)dev->file_addr, (unsigned int)dev->VolCatInfo.EndBlock);
-            mark_volume_in_error(dcr);
-            goto mount_next_vol;
+      } else {
+         Jmsg(jcr, M_ERROR, 0, _("Bacula cannot write on tape Volume \"%s\" because:\n"
+              "The number of files mismatch! Volume=%u Catalog=%u\n"),
+              VolumeName, dev->get_file(), dev->VolCatInfo.VolCatFiles);
+         mark_volume_in_error();
+         return false;
+      }
+   /*
+    * File device.
+    */
+   } else if (dev->is_file()) {
+      char ed1[50], ed2[50];
+      boffset_t pos;
+
+      pos = dev->lseek(this, (boffset_t)0, SEEK_END);
+      if (dev->VolCatInfo.VolCatAmetaBytes == (uint64_t)pos) {
+         Jmsg(jcr, M_INFO, 0, _("Ready to append to end of Volume \"%s\""
+              " size=%s\n"), VolumeName,
+              edit_uint64_with_commas(dev->VolCatInfo.VolCatAmetaBytes, ed1));
+      } else if ((uint64_t)pos >= dev->VolCatInfo.VolCatAmetaBytes) {
+         if ((uint64_t)pos != dev->VolCatInfo.VolCatAmetaBytes) {
+            Jmsg(jcr, M_WARNING, 0, _("For Volume \"%s\":\n"
+               "   The sizes do not match! Volume=%s Catalog=%s\n"
+               "   Correcting Catalog\n"),
+               VolumeName, edit_uint64_with_commas(pos, ed1),
+               edit_uint64_with_commas(dev->VolCatInfo.VolCatAmetaBytes, ed2));
          }
+         dev->VolCatInfo.VolCatAmetaBytes = pos;
+         dev->VolCatInfo.VolCatBytes = pos;
+         dev->VolCatInfo.VolCatFiles = (uint32_t)(pos >> 32);
+         if (!dir_update_volume_info(this, false, true)) {
+            Jmsg(jcr, M_WARNING, 0, _("Error updating Catalog\n"));
+            mark_volume_in_error();
+            return false;
+         }
+      } else {
+         Mmsg(jcr->errmsg, _("Bacula cannot write on disk Volume \"%s\" because: "
+              "The sizes do not match! Volume=%s Catalog=%s\n"),
+              VolumeName,
+              edit_uint64_with_commas(pos, ed1),
+              edit_uint64_with_commas(dev->VolCatInfo.VolCatBytes, ed2));
+         Jmsg(jcr, M_ERROR, 0, jcr->errmsg);
+         Dmsg0(100, jcr->errmsg);
+         mark_volume_in_error();
+         return false;
       }
-#endif
-      
-      /* Return an empty block */
-      empty_block(block);             /* we used it for reading so set for write */
    }
-   dev->set_append();
-   Dmsg1(150, "set APPEND, normal return from mount_next_write_volume. dev=%s\n",
-      dev->print_name());
-
    return true;
 }
 
+
 /*
  * If permitted, we label the device, make sure we can do
  *   it by checking that the VolCatBytes is zero => not labeled,
@@ -475,51 +751,66 @@ read_volume:
  *   try_error           hard error (catalog update)
  *   try_default         I couldn't do anything
  */
-static int try_autolabel(DCR *dcr, bool opened)
+int DCR::try_autolabel(bool opened)
 {
-   DEVICE *dev = dcr->dev;
+   DCR *dcr = this;
 
    if (dev->poll && !dev->is_tape()) {
+      Dmsg0(100, "No autolabel because polling.\n");
       return try_default;       /* if polling, don't try to create new labels */
    }
    /* For a tape require it to be opened and read before labeling */
-   if (!opened && dev->is_tape()) {
+   if (!opened && (dev->is_tape() || dev->is_null())) {
       return try_default;
    }
-   if (dev->has_cap(CAP_LABEL) && (dcr->VolCatInfo.VolCatBytes == 0 ||
-         (!dev->is_tape() && strcmp(dcr->VolCatInfo.VolCatStatus,
+   if (dev->has_cap(CAP_LABEL) && (VolCatInfo.VolCatBytes == 0 ||
+         (!dev->is_tape() && strcmp(VolCatInfo.VolCatStatus,
                                 "Recycle") == 0))) {
-      Dmsg0(150, "Create volume label\n");
+      Dmsg1(40, "Create new volume label vol=%s\n", VolumeName);
       /* Create a new Volume label and write it to the device */
-      if (!write_new_volume_label_to_dev(dcr, dcr->VolumeName,
-             dcr->pool_name, false, /* no relabel */ false /* defer DVD label */)) {
-         Dmsg0(150, "!write_vol_label\n");
-         if (opened) { 
-            mark_volume_in_error(dcr);
+      if (!write_new_volume_label_to_dev(dcr, VolumeName,
+             pool_name, false, /* no relabel */ false /* defer DVD label */)) {
+         Dmsg2(100, "write_vol_label failed. vol=%s, pool=%s\n",
+           VolumeName, pool_name);
+         if (opened) {
+            mark_volume_in_error();
          }
          return try_next_vol;
       }
       Dmsg0(150, "dir_update_vol_info. Set Append\n");
       /* Copy Director's info into the device info */
-      dev->VolCatInfo = dcr->VolCatInfo;    /* structure assignment */
-      if (!dir_update_volume_info(dcr, true)) {  /* indicate tape labeled */
+      dev->VolCatInfo = VolCatInfo;    /* structure assignment */
+      if (!dir_update_volume_info(dcr, true, true)) {  /* indicate tape labeled */
+         Dmsg3(100, "Update_vol_info failed no autolabel Volume \"%s\" on %s device %s.\n",
+            VolumeName, dev->print_type(), dev->print_name());
          return try_error;
       }
-      Jmsg(dcr->jcr, M_INFO, 0, _("Labeled new Volume \"%s\" on device %s.\n"),
-         dcr->VolumeName, dev->print_name());
+      Jmsg(dcr->jcr, M_INFO, 0, _("Labeled new Volume \"%s\" on %s device %s.\n"),
+         VolumeName, dev->print_type(), dev->print_name());
+      Dmsg3(100, "Labeled new Volume \"%s\" on %s device %s.\n",
+         VolumeName, dev->print_type(), dev->print_name());
       return try_read_vol;   /* read label we just wrote */
+   } else {
+      Dmsg4(40, "=== Cannot autolabel: cap_label=%d VolCatBytes=%lld is_tape=%d VolCatStatus=%s\n",
+         dev->has_cap(CAP_LABEL), VolCatInfo.VolCatBytes, dev->is_tape(),
+         VolCatInfo.VolCatStatus);
    }
-   if (!dev->has_cap(CAP_LABEL) && dcr->VolCatInfo.VolCatBytes == 0) {
-      Jmsg(dcr->jcr, M_WARNING, 0, _("Device %s not configured to autolabel Volumes.\n"), 
-         dev->print_name());
+   if (!dev->has_cap(CAP_LABEL) && VolCatInfo.VolCatBytes == 0) {
+      Jmsg(jcr, M_WARNING, 0, _("%s device %s not configured to autolabel Volumes.\n"),
+         dev->print_type(), dev->print_name());
    }
+#ifdef xxx
    /* If not removable, Volume is broken */
    if (!dev->is_removable()) {
-      Jmsg(dcr->jcr, M_WARNING, 0, _("Volume \"%s\" not on device %s.\n"),
-         dcr->VolumeName, dev->print_name());
-      mark_volume_in_error(dcr);
+      Jmsg3(jcr, M_WARNING, 0, _("Volume \"%s\" not loaded on %s device %s.\n"),
+         VolumeName, dev->print_type(), dev->print_name());
+      Dmsg3(40, "Volume \"%s\" not loaded on %s device %s.\n",
+         VolumeName, dev->print_type(), dev->print_name());
+
+      mark_volume_in_error();
       return try_next_vol;
    }
+#endif
    return try_default;
 }
 
@@ -527,74 +818,114 @@ static int try_autolabel(DCR *dcr, bool opened)
 /*
  * Mark volume in error in catalog
  */
-void mark_volume_in_error(DCR *dcr)
+void DCR::mark_volume_in_error()
 {
-   DEVICE *dev = dcr->dev;
-   Jmsg(dcr->jcr, M_INFO, 0, _("Marking Volume \"%s\" in Error in Catalog.\n"),
-        dcr->VolumeName);
-   dev->VolCatInfo = dcr->VolCatInfo;     /* structure assignment */
-   bstrncpy(dev->VolCatInfo.VolCatStatus, "Error", sizeof(dev->VolCatInfo.VolCatStatus));
+   Jmsg(jcr, M_INFO, 0, _("Marking Volume \"%s\" in Error in Catalog.\n"),
+        VolumeName);
+   dev->VolCatInfo = VolCatInfo;       /* structure assignment */
+   dev->setVolCatStatus("Error");
    Dmsg0(150, "dir_update_vol_info. Set Error.\n");
-   dir_update_volume_info(dcr, false);
+   dir_update_volume_info(this, false, false);
+   volume_unused(this);
+   Dmsg0(50, "set_unload\n");
+   dev->set_unload();                 /* must get a new volume */
 }
 
 /*
  * The Volume is not in the correct slot, so mark this
  *   Volume as not being in the Changer.
  */
-static void mark_volume_not_inchanger(DCR *dcr)
+void DCR::mark_volume_not_inchanger()
 {
-   JCR *jcr = dcr->jcr;
-   DEVICE *dev = dcr->dev;
    Jmsg(jcr, M_ERROR, 0, _("Autochanger Volume \"%s\" not found in slot %d.\n"
 "    Setting InChanger to zero in catalog.\n"),
-        dcr->VolCatInfo.VolCatName, dcr->VolCatInfo.Slot);
-   dev->VolCatInfo = dcr->VolCatInfo;    /* structure assignment */
-   dcr->VolCatInfo.InChanger = false;
+        getVolCatName(), VolCatInfo.Slot);
+   dev->VolCatInfo = VolCatInfo;    /* structure assignment */
+   VolCatInfo.InChanger = false;
    dev->VolCatInfo.InChanger = false;
    Dmsg0(400, "update vol info in mount\n");
-   dir_update_volume_info(dcr, true);  /* set new status */
+   dir_update_volume_info(this, true, false);  /* set new status */
 }
 
 /*
  * Either because we are going to hang a new volume, or because
  *  of explicit user request, we release the current volume.
  */
-void release_volume(DCR *dcr)
+void DCR::release_volume()
 {
-   JCR *jcr = dcr->jcr;
-   DEVICE *dev = dcr->dev;
-   if (dcr->WroteVol) {
+   unload_autochanger(this, -1);
+
+   if (WroteVol) {
       Jmsg0(jcr, M_ERROR, 0, _("Hey!!!!! WroteVol non-zero !!!!!\n"));
-      Dmsg0(190, "Hey!!!!! WroteVol non-zero !!!!!\n");
+      Pmsg0(190, "Hey!!!!! WroteVol non-zero !!!!!\n");
    }
+
+   if (dev->is_open() && (!dev->is_tape() || !dev->has_cap(CAP_ALWAYSOPEN))) {
+      generate_plugin_event(jcr, bsdEventDeviceClose, this);
+      dev->close();
+   }
+
+   /* If we have not closed the device, then at least rewind the tape */
+   if (dev->is_open()) {
+      dev->offline_or_rewind();
+   }
+
    /*
-    * First erase all memory of the current volume
+    * Erase all memory of the current volume
     */
+   free_volume(dev);
    dev->block_num = dev->file = 0;
    dev->EndBlock = dev->EndFile = 0;
    memset(&dev->VolCatInfo, 0, sizeof(dev->VolCatInfo));
-   memset(&dcr->VolCatInfo, 0, sizeof(dcr->VolCatInfo));
-   free_volume(dev);
-   memset(&dev->VolHdr, 0, sizeof(dev->VolHdr));
+   dev->clear_volhdr();
    /* Force re-read of label */
    dev->clear_labeled();
    dev->clear_read();
    dev->clear_append();
    dev->label_type = B_BACULA_LABEL;
-   dcr->VolumeName[0] = 0;
+   VolumeName[0] = 0;
 
-   if (dev->is_open() && (!dev->is_tape() || !dev->has_cap(CAP_ALWAYSOPEN))) {
-      dev->close();
-   }
+   Dmsg0(190, "release_volume\n");
+}
 
-   /* If we have not closed the device, then at least rewind the tape */
-   if (dev->is_open()) {
-      dev->offline_or_rewind();
+/*
+ *      Insanity check
+ *
+ * Check to see if the tape position as defined by the OS is
+ *  the same as our concept.  If it is not,
+ *  it means the user has probably manually rewound the tape.
+ * Note, we check only if num_writers == 0, but this code will
+ *  also work fine for any number of writers. If num_writers > 0,
+ *  we probably should cancel all jobs using this device, or
+ *  perhaps even abort the SD, or at a minimum, mark the tape
+ *  in error.  Another strategy with num_writers == 0, would be
+ *  to rewind the tape and do a new eod() request.
+ */
+bool DCR::is_tape_position_ok()
+{
+   if (dev->is_tape() && dev->num_writers == 0) {
+      int32_t file = dev->get_os_tape_file();
+      if (file >= 0 && file != (int32_t)dev->get_file()) {
+         Jmsg(jcr, M_ERROR, 0, _("Invalid tape position on volume \"%s\""
+              " on device %s. Expected %d, got %d\n"),
+              dev->VolHdr.VolumeName, dev->print_name(), dev->get_file(), file);
+         /*
+          * If the current file is greater than zero, it means we probably
+          *  have some bad count of EOF marks, so mark tape in error.  Otherwise
+          *  the operator might have moved the tape, so we just release it
+          *  and try again.
+          */
+         if (file > 0) {
+            mark_volume_in_error();
+         }
+         release_volume();
+         return false;
+      }
    }
-   Dmsg0(190, "release_volume\n");
+   return true;
 }
 
+
 /*
  * If we are reading, we come here at the end of the tape
  *  and see if there are more volumes to be mounted.
@@ -604,14 +935,21 @@ bool mount_next_read_volume(DCR *dcr)
    DEVICE *dev = dcr->dev;
    JCR *jcr = dcr->jcr;
    Dmsg2(90, "NumReadVolumes=%d CurReadVolume=%d\n", jcr->NumReadVolumes, jcr->CurReadVolume);
+
+   volume_unused(dcr);                /* release current volume */
    /*
     * End Of Tape -- mount next Volume (if another specified)
     */
    if (jcr->NumReadVolumes > 1 && jcr->CurReadVolume < jcr->NumReadVolumes) {
+      dev->Lock();
       dev->close();
+      dev->set_read();
+      dcr->set_reserved_for_read();
+      dev->Unlock();
       if (!acquire_device_for_read(dcr)) {
-         Jmsg2(jcr, M_FATAL, 0, _("Cannot open Dev=%s, Vol=%s\n"), dev->print_name(),
-               dcr->VolumeName);
+         Jmsg3(jcr, M_FATAL, 0, _("Cannot open %s Dev=%s, Vol=%s for reading.\n"),
+            dev->print_type(), dev->print_name(), dcr->VolumeName);
+         jcr->setJobStatus(JS_FatalError); /* Jmsg is not working for *SystemJob* */
          return false;
       }
       return true;                    /* next volume mounted */