]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/stored/acquire.c
Fix #1695 about bacula-sd crash in detach_dcr_from_dev()
[bacula/bacula] / bacula / src / stored / acquire.c
index cbd7616f3084a4b6f18374405ce5c267200798ec..146aa4530c7ebdee36f4558d05668ffc41cfc1e6 100644 (file)
@@ -1,12 +1,12 @@
 /*
    Bacula® - The Network Backup Solution
 
-   Copyright (C) 2002-2007 Free Software Foundation Europe e.V.
+   Copyright (C) 2002-2010 Free Software Foundation Europe e.V.
 
    The main author of Bacula is Kern Sibbald, with contributions from
    many others, a complete list can be found in the file AUTHORS.
    This program is Free Software; you can redistribute it and/or
-   modify it under the terms of version two of the GNU General Public
+   modify it under the terms of version three of the GNU Affero General Public
    License as published by the Free Software Foundation and included
    in the file LICENSE.
 
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    General Public License for more details.
 
-   You should have received a copy of the GNU General Public License
+   You should have received a copy of the GNU Affero General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
 
-   Bacula® is a registered trademark of John Walker.
+   Bacula® is a registered trademark of Kern Sibbald.
    The licensor of Bacula is the Free Software Foundation Europe
    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
    Switzerland, email:ftf@fsfeurope.org.
@@ -30,7 +30,6 @@
  *
  *   Kern Sibbald, August MMII
  *
- *   Version $Id$
  */
 
 #include "bacula.h"                   /* pull in global headers */
@@ -38,7 +37,8 @@
 
 /* Forward referenced functions */
 static void attach_dcr_to_dev(DCR *dcr);
-static bool is_suitable_volume_mounted(DCR *dcr);
+static void detach_dcr_from_dev(DCR *dcr);
+static void set_dcr_from_vol(DCR *dcr, VOL_LIST *vol);
 
 
 /*********************************************************************
@@ -63,7 +63,8 @@ bool acquire_device_for_read(DCR *dcr)
    int vol_label_status;
    int retry = 0;
    
-   Dmsg1(950, "jcr->dcr=%p\n", jcr->dcr);
+   Dmsg2(950, "dcr=%p dev=%p\n", dcr, dcr->dev);
+   Dmsg2(950, "MediaType dcr=%s dev=%s\n", dcr->media_type, dev->device->media_type);
    dev->dblock(BST_DOING_ACQUIRE);
 
    if (dev->num_writers > 0) {
@@ -89,9 +90,9 @@ bool acquire_device_for_read(DCR *dcr)
          jcr->NumReadVolumes, jcr->CurReadVolume);
       goto get_out;                   /* should not happen */   
    }
-   bstrncpy(dcr->VolumeName, vol->VolumeName, sizeof(dcr->VolumeName));
-   bstrncpy(dcr->media_type, vol->MediaType, sizeof(dcr->media_type));
-   dcr->VolCatInfo.Slot = vol->Slot;
+   set_dcr_from_vol(dcr, vol);
+
+   Dmsg2(100, "Want Vol=%s Slot=%d\n", vol->VolumeName, vol->Slot);
     
    /*
     * If the MediaType requested for this volume is not the
@@ -111,10 +112,10 @@ bool acquire_device_for_read(DCR *dcr)
       DIRSTORE *store;
       int stat;
 
-      Jmsg3(jcr, M_INFO, 0, _("Changing device. Want Media Type=\"%s\" have=\"%s\"\n"
+      Jmsg3(jcr, M_INFO, 0, _("Changing read device. Want Media Type=\"%s\" have=\"%s\"\n"
                               "  device=%s\n"), 
             dcr->media_type, dev->device->media_type, dev->print_name());
-      Dmsg3(50, "Changing device. Want Media Type=\"%s\" have=\"%s\"\n"
+      Dmsg3(50, "Changing read device. Want Media Type=\"%s\" have=\"%s\"\n"
                               "  device=%s\n", 
             dcr->media_type, dev->device->media_type, dev->print_name());
 
@@ -123,6 +124,7 @@ bool acquire_device_for_read(DCR *dcr)
       lock_reservations();
       memset(&rctx, 0, sizeof(RCTX));
       rctx.jcr = jcr;
+      jcr->read_dcr = dcr;
       jcr->reserve_msgs = New(alist(10, not_owned_by_alist));
       rctx.any_drive = true;
       rctx.device_name = vol->device;
@@ -147,13 +149,15 @@ bool acquire_device_for_read(DCR *dcr)
          dev = dcr->dev;                     /* get new device pointer */
          dev->dblock(BST_DOING_ACQUIRE); 
          dcr->VolumeName[0] = 0;
-         Jmsg(jcr, M_INFO, 0, _("Media Type change.  New device %s chosen.\n"),
+         Jmsg(jcr, M_INFO, 0, _("Media Type change.  New read device %s chosen.\n"),
             dev->print_name());
-         Dmsg1(50, "Media Type change.  New device %s chosen.\n", dev->print_name());
+         Dmsg1(50, "Media Type change.  New read device %s chosen.\n", dev->print_name());
 
          bstrncpy(dcr->VolumeName, vol->VolumeName, sizeof(dcr->VolumeName));
+         dcr->setVolCatName(vol->VolumeName);
          bstrncpy(dcr->media_type, vol->MediaType, sizeof(dcr->media_type));
          dcr->VolCatInfo.Slot = vol->Slot;
+         dcr->VolCatInfo.InChanger = vol->Slot > 0; 
          bstrncpy(dcr->pool_name, store->pool_name, sizeof(dcr->pool_name));
          bstrncpy(dcr->pool_type, store->pool_type, sizeof(dcr->pool_type));
       } else {
@@ -164,7 +168,15 @@ bool acquire_device_for_read(DCR *dcr)
          goto get_out;
       }
    }
+   Dmsg2(400, "MediaType dcr=%s dev=%s\n", dcr->media_type, dev->device->media_type);
+
+   dev->clear_unload();
 
+   if (dev->vol && dev->vol->is_swapping()) {
+      dev->vol->set_slot(vol->Slot);
+      Dmsg3(100, "swapping: slot=%d Vol=%s dev=%s\n", dev->vol->get_slot(),
+         dev->vol->vol_name, dev->print_name());
+   }
 
    init_device_wait_timers(dcr);
 
@@ -174,10 +186,13 @@ bool acquire_device_for_read(DCR *dcr)
 
 
    /* Volume info is always needed because of VolParts */
-   Dmsg0(200, "dir_get_volume_info\n");
+   Dmsg1(150, "dir_get_volume_info vol=%s\n", dcr->VolumeName);
    if (!dir_get_volume_info(dcr, GET_VOL_INFO_FOR_READ)) {
-      Jmsg1(jcr, M_WARNING, 0, "%s", jcr->errmsg);
+      Dmsg2(150, "dir_get_vol_info failed for vol=%s: %s\n", 
+         dcr->VolumeName, jcr->errmsg);
+      Jmsg1(jcr, M_WARNING, 0, "Read acquire: %s", jcr->errmsg);
    }
+   dev->set_load();                /* set to load volume */
    
    for ( ;; ) {
       /* If not polling limit retries */
@@ -192,7 +207,10 @@ bool acquire_device_for_read(DCR *dcr)
          goto get_out;                /* error return */
       }
 
-      autoload_device(dcr, 0, NULL);
+      dcr->do_unload();
+      dcr->do_swapping(false/*!is_writing*/);
+      dcr->do_load(false /*!is_writing*/);
+      set_dcr_from_vol(dcr, vol);          /* refresh dcr with desired volume info */
 
       /*
        * This code ensures that the device is ready for
@@ -201,8 +219,10 @@ bool acquire_device_for_read(DCR *dcr)
        */
       Dmsg1(100, "bstored: open vol=%s\n", dcr->VolumeName);
       if (dev->open(dcr, OPEN_READ_ONLY) < 0) {
-        Jmsg3(jcr, M_WARNING, 0, _("Read open device %s Volume \"%s\" failed: ERR=%s\n"),
-              dev->print_name(), dcr->VolumeName, dev->bstrerror());
+         if (!dev->poll) {
+            Jmsg3(jcr, M_WARNING, 0, _("Read open device %s Volume \"%s\" failed: ERR=%s\n"),
+                  dev->print_name(), dcr->VolumeName, dev->bstrerror());
+         }
          goto default_path;
       }
       Dmsg1(50, "opened dev %s OK\n", dev->print_name());
@@ -212,34 +232,39 @@ bool acquire_device_for_read(DCR *dcr)
       vol_label_status = read_dev_volume_label(dcr);
       switch (vol_label_status) {
       case VOL_OK:
+         Dmsg0(50, "Got correct volume.\n");
          ok = true;
-         memcpy(&dev->VolCatInfo, &dcr->VolCatInfo, sizeof(dev->VolCatInfo));
+         dev->VolCatInfo = dcr->VolCatInfo;     /* structure assignment */
          break;                    /* got it */
       case VOL_IO_ERROR:
+         Dmsg0(50, "IO Error\n");
          /*
           * Send error message generated by read_dev_volume_label()
           *  only we really had a tape mounted. This supresses superfluous
           *  error messages when nothing is mounted.
           */
          if (tape_previously_mounted) {
-            Jmsg(jcr, M_WARNING, 0, "%s", jcr->errmsg);
+            Jmsg(jcr, M_WARNING, 0, "Read acquire: %s", jcr->errmsg);
          }
          goto default_path;
       case VOL_NAME_ERROR:
-         if (tape_initially_mounted) {
-            tape_initially_mounted = false;
+         Dmsg3(50, "Vol name=%s want=%s drv=%s.\n", dev->VolHdr.VolumeName, 
+               dcr->VolumeName, dev->print_name());
+         if (dev->is_volume_to_unload()) {
             goto default_path;
          }
-         /* If polling and got a previous bad name, ignore it */
-         if (dev->poll && strcmp(dev->BadVolName, dev->VolHdr.VolumeName) == 0) {
-            goto default_path;
-         } else {
-             bstrncpy(dev->BadVolName, dev->VolHdr.VolumeName, sizeof(dev->BadVolName));
+         dev->set_unload();              /* force unload of unwanted tape */
+         if (!unload_autochanger(dcr, -1)) {
+            /* at least free the device so we can re-open with correct volume */
+            dev->close();                                                          
+            free_volume(dev);
          }
+         dev->set_load();
          /* Fall through */
       default:
-         Jmsg1(jcr, M_WARNING, 0, "%s", jcr->errmsg);
+         Jmsg1(jcr, M_WARNING, 0, "Read acquire: %s", jcr->errmsg);
 default_path:
+         Dmsg0(50, "default path\n");
          tape_previously_mounted = true;
          
          /*
@@ -247,6 +272,7 @@ default_path:
           */
          if (dev->requires_mount()) {
             dev->close();
+            free_volume(dev);
          }
          
          /* Call autochanger only once unless ask_sysop called */
@@ -263,14 +289,26 @@ default_path:
          
          /* Mount a specific volume and no other */
          Dmsg0(200, "calling dir_ask_sysop\n");
-         if (!dir_ask_sysop_to_mount_volume(dcr)) {
+         if (!dir_ask_sysop_to_mount_volume(dcr, ST_READ)) {
             goto get_out;             /* error return */
          }
-         try_autochanger = true;      /* permit using autochanger again */
+
+         /* Volume info is always needed because of VolParts */
+         Dmsg1(150, "dir_get_volume_info vol=%s\n", dcr->VolumeName);
+         if (!dir_get_volume_info(dcr, GET_VOL_INFO_FOR_READ)) {
+            Dmsg2(150, "dir_get_vol_info failed for vol=%s: %s\n", 
+                  dcr->VolumeName, jcr->errmsg);
+            Jmsg1(jcr, M_WARNING, 0, "Read acquire: %s", jcr->errmsg);
+         }
+         dev->set_load();                /* set to load volume */
+
+         try_autochanger = true;      /* permit trying the autochanger again */
+
          continue;                    /* try reading again */
       } /* end switch */
       break;
    } /* end for loop */
+
    if (!ok) {
       Jmsg1(jcr, M_FATAL, 0, _("Too many errors trying to mount device %s for reading.\n"),
             dev->print_name());
@@ -286,11 +324,7 @@ default_path:
 
 get_out:
    dev->dlock();
-   if (dcr && dcr->reserved_device) {
-      dev->reserved_device--;
-      Dmsg2(50, "Dec reserve=%d dev=%s\n", dev->reserved_device, dev->print_name());
-      dcr->reserved_device = false;
-   }
+   dcr->clear_reserved();
    /* 
     * Normally we are blocked, but in at least one error case above 
     *   we are not blocked because we unsuccessfully tried changing
@@ -298,12 +332,14 @@ get_out:
     */
    if (dev->is_blocked()) {
       dev->dunblock(DEV_LOCKED);
+   } else {
+      dev->dunlock();               /* dunblock() unlock the device too */
    }
-   Dmsg1(950, "jcr->dcr=%p\n", jcr->dcr);
+   Dmsg2(950, "dcr=%p dev=%p\n", dcr, dcr->dev);
+   Dmsg2(950, "MediaType dcr=%s dev=%s\n", dcr->media_type, dev->device->media_type);
    return ok;
 }
 
-
 /*
  * Acquire device for writing. We permit multiple writers.
  *  If this is the first one, we read the label.
@@ -315,16 +351,16 @@ get_out:
  */
 DCR *acquire_device_for_append(DCR *dcr)
 {
-   bool do_mount = false;
-   bool release = false;
-   bool have_vol;
    DEVICE *dev = dcr->dev;
    JCR *jcr = dcr->jcr;
+   bool ok = false;
+   bool have_vol = false;
 
    init_device_wait_timers(dcr);
 
-   dev->dblock(BST_DOING_ACQUIRE);
-   Dmsg1(190, "acquire_append device is %s\n", dev->is_tape()?"tape":
+   P(dev->acquire_mutex);           /* only one job at a time */
+   dev->dlock();
+   Dmsg1(100, "acquire_append device is %s\n", dev->is_tape()?"tape":
         (dev->is_dvd()?"DVD":"disk"));
 
    /*
@@ -336,99 +372,46 @@ DCR *acquire_device_for_append(DCR *dcr)
       goto get_out;
    }
 
+   dev->clear_unload();
+
    /*
     * have_vol defines whether or not mount_next_write_volume should
     *   ask the Director again about what Volume to use.
     */
-   have_vol = is_suitable_volume_mounted(dcr);
-   if (dev->can_append()) {
+   if (dev->can_append() && dcr->is_suitable_volume_mounted() &&
+       strcmp(dcr->VolCatInfo.VolCatStatus, "Recycle") != 0) {
       Dmsg0(190, "device already in append.\n");
       /*
-       * Device already in append mode
-       *
-       * Check if we have the right Volume mounted
-       *   OK if current volume info OK
-       *   OK if next volume matches current volume
-       *   otherwise mount desired volume obtained from
-       *    dir_find_next_appendable_volume
-       *  dev->VolHdr.VolumeName is what is in the drive
-       *  dcr->VolumeName is what we pass into the routines, or
-       *    get back from the subroutines.
+       * At this point, the correct tape is already mounted, so
+       *   we do not need to do mount_next_write_volume(), unless
+       *   we need to recycle the tape.
        */
-      if (!have_vol &&
-          !(dir_find_next_appendable_volume(dcr) &&
-            strcmp(dev->VolHdr.VolumeName, dcr->VolumeName) == 0)) { /* wrong tape mounted */
-         /* Wrong tape mounted, release it, then fall through to get correct one */
-         Dmsg0(50, "Wrong tape mounted, release and try mount.\n");
-         release = true;
-         do_mount = true;
-      } else {
-         /*
-          * At this point, the correct tape is already mounted, so
-          *   we do not need to do mount_next_write_volume(), unless
-          *   we need to recycle the tape.
-          */
-          do_mount = strcmp(dcr->VolCatInfo.VolCatStatus, "Recycle") == 0;
-          Dmsg2(190, "jid=%u Correct tape mounted. recycle=%d\n", 
-                (uint32_t)jcr->JobId, do_mount);
-#ifdef xxx
-          if (do_mount && dev->num_writers != 0) {
-             Jmsg(jcr, M_FATAL, 0, _("Cannot recycle volume \"%s\""
-                  " on device %s because it is in use by another job.\n"),
-                  dev->VolHdr.VolumeName, dev->print_name());
-             goto get_out;
-          }
-#endif
-          if (dev->num_writers == 0) {
-             memcpy(&dev->VolCatInfo, &dcr->VolCatInfo, sizeof(dev->VolCatInfo));
-          }
-
-          /*
-           *      Insanity check 
-           *
-           * Check to see if the tape position as defined by the OS is
-           *  the same as our concept.  If it is not, we bail out, because
-           *  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.
-           */
-          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_FATAL, 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);
-                goto get_out;
-             }
-          }
-      }
-   } else {
-      /* Not already in append mode, so mount the device */
-      Dmsg2(190, "jid=%u Not in append mode, try mount have_vol=%d\n", 
-            (uint32_t)jcr->JobId, have_vol);
-
-      ASSERT(dev->num_writers == 0);
-      do_mount = true;
+       if (dev->num_writers == 0) {
+          dev->VolCatInfo = dcr->VolCatInfo;   /* structure assignment */
+       }
+       have_vol = dcr->is_tape_position_ok();
    }
 
-   if (do_mount || !have_vol) {
+   if (!have_vol) {
+      dev->r_dlock(true);
+      block_device(dev, BST_DOING_ACQUIRE);
+      dev->dunlock();
       Dmsg1(190, "jid=%u Do mount_next_write_vol\n", (uint32_t)jcr->JobId);
-      bool mounted = mount_next_write_volume(dcr, have_vol, release);
-      if (!mounted) {
+      if (!dcr->mount_next_write_volume()) {
          if (!job_canceled(jcr)) {
             /* Reduce "noise" -- don't print if job canceled */
             Jmsg(jcr, M_FATAL, 0, _("Could not ready device %s for append.\n"),
                dev->print_name());
-            Dmsg2(200, "jid=%u Could not ready device %s for append.\n", 
-               (uint32_t)jcr->JobId, dev->print_name());
+            Dmsg1(200, "Could not ready device %s for append.\n", 
+               dev->print_name());
          }
+         dev->dlock();
+         unblock_device(dev);
          goto get_out;
       }
       Dmsg2(190, "Output pos=%u:%u\n", dcr->dev->file, dcr->dev->block_num);
+      dev->dlock();
+      unblock_device(dev);
    }
 
    dev->num_writers++;                /* we are now a writer */
@@ -436,51 +419,26 @@ DCR *acquire_device_for_append(DCR *dcr)
       jcr->NumWriteVolumes = 1;
    }
    dev->VolCatInfo.VolCatJobs++;              /* increment number of jobs on vol */
+   Dmsg4(100, "=== nwriters=%d nres=%d vcatjob=%d dev=%s\n", 
+      dev->num_writers, dev->num_reserved(), dev->VolCatInfo.VolCatJobs, 
+      dev->print_name());
    dir_update_volume_info(dcr, false, false); /* send Volume info to Director */
-   dev->dlock();
-   if (dcr->reserved_device) {
-      dev->reserved_device--;
-      Dmsg3(100, "jid=%u Dec reserve=%d dev=%s\n", (uint32_t)jcr->JobId,
-            dev->reserved_device, dev->print_name());
-      dcr->reserved_device = false;
-   }
-   dev->dunblock(DEV_LOCKED);
-   return dcr;
+   ok = true;
 
-/*
- * Error return
- */
 get_out:
-   dev->dlock();
-   if (dcr->reserved_device) {
-      dev->reserved_device--;
-      Dmsg3(100, "jid=%u Dec reserve=%d dev=%s\n", (uint32_t)jcr->JobId, 
-            dev->reserved_device, dev->print_name());
-      dcr->reserved_device = false;
-   }
-   dev->dunblock(DEV_LOCKED);
-   return NULL;
-}
-
-
-static bool is_suitable_volume_mounted(DCR *dcr)
-{
-   DEVICE *dev = dcr->dev;
-
-   /* Volume mounted? */
-   if (dev->VolHdr.VolumeName[0] == 0) {
-      return false;                      /* no */
-   }
-   bstrncpy(dcr->VolumeName, dev->VolHdr.VolumeName, sizeof(dcr->VolumeName));
-   return dir_get_volume_info(dcr, GET_VOL_INFO_FOR_WRITE);
+   dcr->clear_reserved();
+   dev->dunlock();
+   V(dev->acquire_mutex);
+   return ok ? dcr : NULL;
 }
 
 /*
  * This job is done, so release the device. From a Unix standpoint,
  *  the device remains open.
  *
- * Note, if we are spooling, we may enter with the device locked.
- * However, in all cases, unlock the device when leaving.
+ * Note, if we were spooling, we may enter with the device blocked.
+ *   We unblock at the end, only if it was us who blocked the
+ *   device.
  *
  */
 bool release_device(DCR *dcr)
@@ -488,25 +446,32 @@ bool release_device(DCR *dcr)
    JCR *jcr = dcr->jcr;
    DEVICE *dev = dcr->dev;
    bool ok = true;
+   char tbuf[100];
+   int was_blocked = BST_NOT_BLOCKED;
 
-   /* lock only if not already locked by this thread */
-   if (!dcr->is_dev_locked()) {
-      dev->r_dlock();
+   dev->dlock();
+   if (!dev->is_blocked()) {
+      block_device(dev, BST_RELEASING);
+   } else {
+      was_blocked = dev->blocked();
+      dev->set_blocked(BST_RELEASING);
    }
+   lock_volumes();
    Dmsg2(100, "release_device device %s is %s\n", dev->print_name(), dev->is_tape()?"tape":"disk");
 
    /* if device is reserved, job never started, so release the reserve here */
-   if (dcr->reserved_device) {
-      dev->reserved_device--;
-      Dmsg2(100, "Dec reserve=%d dev=%s\n", dev->reserved_device, dev->print_name());
-      dcr->reserved_device = false;
-   }
+   dcr->clear_reserved();
 
    if (dev->can_read()) {
+      VOLUME_CAT_INFO *vol = &dev->VolCatInfo;
       dev->clear_read();              /* clear read bit */
-      Dmsg0(100, "dir_update_vol_info. Release0\n");
-      dir_update_volume_info(dcr, false, false); /* send Volume info to Director */
-
+      Dmsg2(150, "dir_update_vol_info. label=%d Vol=%s\n",
+         dev->is_labeled(), vol->VolCatName);
+      if (dev->is_labeled() && vol->VolCatName[0] != 0) {
+         dir_update_volume_info(dcr, false, false); /* send Volume info to Director */
+         remove_read_volume(jcr, dcr->VolumeName);
+         volume_unused(dcr);
+      }
    } else if (dev->num_writers > 0) {
       /* 
        * Note if WEOT is set, we are at the end of the tape
@@ -517,12 +482,13 @@ bool release_device(DCR *dcr)
       dev->num_writers--;
       Dmsg1(100, "There are %d writers in release_device\n", dev->num_writers);
       if (dev->is_labeled()) {
-         Dmsg0(100, "dir_create_jobmedia_record. Release\n");
+         Dmsg2(200, "dir_create_jobmedia. Release vol=%s dev=%s\n", 
+               dev->getVolCatName(), dev->print_name());
          if (!dev->at_weot() && !dir_create_jobmedia_record(dcr)) {
-            Jmsg(jcr, M_FATAL, 0, _("Could not create JobMedia record for Volume=\"%s\" Job=%s\n"),
-               dcr->VolCatInfo.VolCatName, jcr->Job);
+            Jmsg2(jcr, M_FATAL, 0, _("Could not create JobMedia record for Volume=\"%s\" Job=%s\n"),
+               dcr->getVolCatName(), jcr->Job);
          }
-         /* If no more writers, write an EOF */
+         /* If no more writers, and no errors, and wrote something, write an EOF */
          if (!dev->num_writers && dev->can_write() && dev->block_num > 0) {
             dev->weof(1);
             write_ansi_ibm_labels(dcr, ANSI_EOF_LABEL, dev->VolHdr.VolumeName);
@@ -530,8 +496,12 @@ bool release_device(DCR *dcr)
          if (!dev->at_weot()) {
             dev->VolCatInfo.VolCatFiles = dev->file;   /* set number of files */
             /* Note! do volume update before close, which zaps VolCatInfo */
-            Dmsg0(100, "dir_update_vol_info. Release0\n");
             dir_update_volume_info(dcr, false, false); /* send Volume info to Director */
+            Dmsg2(200, "dir_update_vol_info. Release vol=%s dev=%s\n", 
+                  dev->getVolCatName(), dev->print_name());
+         }
+         if (dev->num_writers == 0) {         /* if not being used */
+            volume_unused(dcr);               /*  we obviously are not using the volume */
          }
       }
 
@@ -541,12 +511,16 @@ bool release_device(DCR *dcr)
        *   has failed, since the device is not in read mode and
        *   there are no writers. It was probably reserved.
        */
+      volume_unused(dcr);
    }
+   Dmsg3(100, "%d writers, %d reserve, dev=%s\n", dev->num_writers, dev->num_reserved(),
+         dev->print_name());
 
    /* If no writers, close if file or !CAP_ALWAYS_OPEN */
    if (dev->num_writers == 0 && (!dev->is_tape() || !dev->has_cap(CAP_ALWAYSOPEN))) {
       dvd_remove_empty_part(dcr);        /* get rid of any empty spool part */
       dev->close();
+      free_volume(dev);
    }
 
    /* Fire off Alert command and include any output */
@@ -557,7 +531,8 @@ bool release_device(DCR *dcr)
       char line[MAXSTRING];
       alert = get_pool_memory(PM_FNAME);
       alert = edit_device_codes(dcr, alert, dcr->device->alert_command, "");
-      bpipe = open_bpipe(alert, 0, "r");
+      /* Wait maximum 5 minutes */
+      bpipe = open_bpipe(alert, 60 * 5, "r");
       if (bpipe) {
          while (fgets(line, sizeof(line), bpipe->rfd)) {
             Jmsg(jcr, M_ALERT, 0, _("Alert: %s"), line);
@@ -576,18 +551,25 @@ bool release_device(DCR *dcr)
       free_pool_memory(alert);
    }
    pthread_cond_broadcast(&dev->wait_next_vol);
-   Dmsg1(100, "JobId=%u broadcast wait_device_release\n", (uint32_t)jcr->JobId);
+   Dmsg2(100, "JobId=%u broadcast wait_device_release at %s\n", 
+         (uint32_t)jcr->JobId, bstrftimes(tbuf, sizeof(tbuf), (utime_t)time(NULL)));
    pthread_cond_broadcast(&wait_device_release);
-   dev->dunlock();
+   unlock_volumes();
+
+   /*
+    * If we are the thread that blocked the device, then unblock it
+    */
+   if (pthread_equal(dev->no_wait_id, pthread_self())) {
+      dev->dunblock(true);
+   } else {
+      /* Otherwise, reset the prior block status and unlock */
+      dev->set_blocked(was_blocked);
+      dev->dunlock();
+   }
+
    if (dcr->keep_dcr) {
       detach_dcr_from_dev(dcr);
    } else {
-      if (jcr->read_dcr == dcr) {
-         jcr->read_dcr = NULL;
-      }
-      if (jcr->dcr == dcr) {
-         jcr->dcr = NULL;
-      }
       free_dcr(dcr);
    }
    Dmsg2(100, "===== Device %s released by JobId=%u\n", dev->print_name(),
@@ -623,10 +605,17 @@ bool clean_device(DCR *dcr)
 DCR *new_dcr(JCR *jcr, DCR *dcr, DEVICE *dev)
 {
    if (!dcr) {
+      int errstat;
       dcr = (DCR *)malloc(sizeof(DCR));
       memset(dcr, 0, sizeof(DCR));
       dcr->tid = pthread_self();
       dcr->spool_fd = -1;
+      if ((errstat = pthread_mutex_init(&dcr->m_mutex, NULL)) != 0) {
+         berrno be;
+         dev->dev_errno = errstat;
+         Mmsg1(dev->errmsg, _("Unable to init mutex: ERR=%s\n"), be.bstrerror(errstat));
+         Jmsg0(jcr, M_ERROR_TERM, 0, dev->errmsg);
+      }
    }
    dcr->jcr = jcr;                 /* point back to jcr */
    /* Set device information, possibly change device */
@@ -686,31 +675,49 @@ static void remove_dcr_from_dcrs(DCR *dcr)
 
 static void attach_dcr_to_dev(DCR *dcr)
 {
-   DEVICE *dev = dcr->dev;
-   JCR *jcr = dcr->jcr;
+   DEVICE *dev;
+   JCR *jcr;
 
+   P(dcr->m_mutex);
+   dev = dcr->dev;
+   jcr = dcr->jcr;
    if (jcr) Dmsg1(500, "JobId=%u enter attach_dcr_to_dev\n", (uint32_t)jcr->JobId);
-   if (!dcr->attached_to_dev && dev->initiated && jcr && jcr->JobType != JT_SYSTEM) {
+   /* ***FIXME*** return error if dev not initiated */
+   if (!dcr->attached_to_dev && dev->initiated && jcr && jcr->getJobType() != JT_SYSTEM) {
+      dev->dlock();
       dev->attached_dcrs->append(dcr);  /* attach dcr to device */
+      dev->dunlock();
       dcr->attached_to_dev = true;
       Dmsg1(500, "JobId=%u attach_dcr_to_dev\n", (uint32_t)jcr->JobId);
    }
+   V(dcr->m_mutex);
 }
 
-void detach_dcr_from_dev(DCR *dcr)
+/* 
+ * DCR is locked before calling this routine
+ */
+static void locked_detach_dcr_from_dev(DCR *dcr)
 {
    DEVICE *dev = dcr->dev;
-   Dmsg1(500, "JobId=%u enter detach_dcr_from_dev\n", (uint32_t)dcr->jcr->JobId);
+   Dmsg0(500, "Enter detach_dcr_from_dev\n"); /* jcr is NULL in some cases */
 
    /* Detach this dcr only if attached */
    if (dcr->attached_to_dev && dev) {
+      dcr->unreserve_device();
       dev->dlock();
-      unreserve_device(dcr);
       dcr->dev->attached_dcrs->remove(dcr);  /* detach dcr from device */
-      dcr->attached_to_dev = false;
 //    remove_dcr_from_dcrs(dcr);      /* remove dcr from jcr list */
       dev->dunlock();
    }
+   dcr->attached_to_dev = false;
+}
+
+
+static void detach_dcr_from_dev(DCR *dcr)
+{
+   P(dcr->m_mutex);
+   locked_detach_dcr_from_dev(dcr);
+   V(dcr->m_mutex);
 }
 
 /*
@@ -719,9 +726,11 @@ void detach_dcr_from_dev(DCR *dcr)
  */
 void free_dcr(DCR *dcr)
 {
-   JCR *jcr = dcr->jcr;
+   JCR *jcr;
 
-   detach_dcr_from_dev(dcr);
+   P(dcr->m_mutex);
+   jcr = dcr->jcr;
+   locked_detach_dcr_from_dev(dcr);
 
    if (dcr->block) {
       free_block(dcr->block);
@@ -732,5 +741,23 @@ void free_dcr(DCR *dcr)
    if (jcr && jcr->dcr == dcr) {
       jcr->dcr = NULL;
    }
+   if (jcr && jcr->read_dcr == dcr) {
+      jcr->read_dcr = NULL;
+   }
+   V(dcr->m_mutex);
+   pthread_mutex_destroy(&dcr->m_mutex);
    free(dcr);
 }
+
+static void set_dcr_from_vol(DCR *dcr, VOL_LIST *vol)
+{
+   /*    
+    * Note, if we want to be able to work from a .bsr file only          
+    *  for disaster recovery, we must "simulate" reading the catalog
+    */
+   bstrncpy(dcr->VolumeName, vol->VolumeName, sizeof(dcr->VolumeName));
+   dcr->setVolCatName(vol->VolumeName);
+   bstrncpy(dcr->media_type, vol->MediaType, sizeof(dcr->media_type));
+   dcr->VolCatInfo.Slot = vol->Slot;
+   dcr->VolCatInfo.InChanger = vol->Slot > 0; 
+}