/*
    Bacula® - The Network Backup Solution
 
-   Copyright (C) 2000-2012 Free Software Foundation Europe e.V.
+   Copyright (C) 2000-2013 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.
    rwl_destroy(&vol_list_lock);
 }
 
-/* 
+/*
  * This allows a given thread to recursively call to lock_volumes()
  */
 void _lock_volumes(const char *file, int line)
 {
    VOLRES *nvol, *vol;
 
-   lock_read_volumes();
    nvol = new_vol_item(NULL, VolumeName);
    nvol->set_jobid(jcr->JobId);
+   lock_read_volumes();
    vol = (VOLRES *)read_vol_list->binary_insert(nvol, read_compare);
+   unlock_read_volumes();
    if (vol != nvol) {
       free_vol_item(nvol);
       Dmsg2(dbglvl, "read_vol=%s JobId=%d already in list.\n", VolumeName, jcr->JobId);
    } else {
       Dmsg2(dbglvl, "add read_vol=%s JobId=%d\n", VolumeName, jcr->JobId);
    }
-   unlock_read_volumes();
 }
 
 /*
    VOLRES *vol;
    POOL_MEM msg(PM_MESSAGE);
 
-   lock_volumes();
-   foreach_dlist(vol, vol_list) {
+   foreach_vol(vol) {
       if (vol->dev) {
          Mmsg(msg, "List %s: %s in_use=%d swap=%d on device %s\n", imsg, 
               vol->vol_name, vol->is_in_use(), vol->is_swapping(), vol->dev->print_name());
       }
       Dmsg1(dbglvl, "%s", msg.c_str());
    }
-
-   unlock_volumes();
+   endeach_vol(vol);
 }
 
 
    POOL_MEM msg(PM_MESSAGE);
    int len;
 
-   lock_volumes();
-   foreach_dlist(vol, vol_list) {
+   foreach_vol(vol) {
       DEVICE *dev = vol->dev;
       if (dev) {
          len = Mmsg(msg, "%s on device %s\n", vol->vol_name, dev->print_name());
          sendit(msg.c_str(), len, arg);
-         len = Mmsg(msg, "    Reader=%d writers=%d devres=%d volinuse=%d\n", 
-            dev->can_read()?1:0, dev->num_writers, dev->num_reserved(),   
+         len = Mmsg(msg, "    Reader=%d writers=%d reserves=%d volinuse=%d\n",
+            dev->can_read()?1:0, dev->num_writers, dev->num_reserved(),
             vol->is_in_use());
          sendit(msg.c_str(), len, arg);
       } else {
-         len = Mmsg(msg, "%s no device. volinuse= %d\n", vol->vol_name, 
+         len = Mmsg(msg, "Volume %s no device. volinuse= %d\n", vol->vol_name,
             vol->is_in_use());
          sendit(msg.c_str(), len, arg);
       }
    }
-   unlock_volumes();
+   endeach_vol(vol);
 
    lock_read_volumes();
    foreach_dlist(vol, read_vol_list) {
-      len = Mmsg(msg, "%s read volume JobId=%d\n", vol->vol_name, 
-            vol->get_jobid());
-      sendit(msg.c_str(), len, arg);
+      DEVICE *dev = vol->dev;
+      if (dev) {
+         len = Mmsg(msg, "Read volume: %s on device %s\n", vol->vol_name, dev->print_name());
+         sendit(msg.c_str(), len, arg);
+         len = Mmsg(msg, "    Reader=%d writers=%d reserves=%d volinuse=%d JobId=%d\n",
+            dev->can_read()?1:0, dev->num_writers, dev->num_reserved(),
+            vol->is_in_use(), vol->get_jobid());
+         sendit(msg.c_str(), len, arg);
+      } else {
+         len = Mmsg(msg, "Volume: %s no device. volinuse= %d\n", vol->vol_name,
+            vol->is_in_use());
+         sendit(msg.c_str(), len, arg);
+      }
    }
    unlock_read_volumes();
-
 }
 
 /*
       Dmsg3(dbglvl, "new Vol=%s at %p dev=%s\n",
             VolumeName, vol->vol_name, vol->dev->print_name());
    }
+   vol->init_mutex();
+   vol->inc_use_count();
    return vol;
 }
 
 {
    DEVICE *dev = NULL;
 
+   vol->dec_use_count();
+   vol->Lock();
+   if (vol->use_count() > 0) {
+      vol->Unlock();
+      return;
+   }
+   vol->Unlock();
    free(vol->vol_name);
    if (vol->dev) {
       dev = vol->dev;
    }
+   vol->destroy_mutex();
    free(vol);
    if (dev) {
       dev->vol = NULL;
  *
  * Some details of the Volume list handling:
  *
- *  1. The Volume list entry must be attached to the drive (rather than 
- *       attached to a job as it currently is. I.e. the drive that "owns" 
+ *  1. The Volume list entry is attached to the drive (rather than
+ *       attached to a job as it was previously. I.e. the drive that "owns"
  *       the volume (in use, mounted)
  *       must point to the volume (still to be maintained in a list).
  *
- *  2. The Volume is entered in the list when a drive is reserved.  
+ *  2. The Volume is entered in the list when a drive is reserved.
  *
  *  3. When a drive is in use, the device code must appropriately update the
- *      volume name as it changes (currently the list is static -- an entry is
- *      removed when the Volume is no longer reserved, in use or mounted).  
- *      The new code must keep the same list entry as long as the drive
+ *       volume name as it changes.
+  *      This code keeps the same list entry as long as the drive
  *       has any volume associated with it but the volume name in the list
  *       must be updated when the drive has a different volume mounted.
  *
- *  4. A job that has reserved a volume, can un-reserve the volume, and if the 
+ *  4. A job that has reserved a volume, can un-reserve the volume, and if the
  *      volume is not mounted, and not reserved, and not in use, it will be
  *      removed from the list.
  *
  *  5. If a job wants to reserve a drive with a different Volume from the one on
  *      the drive, it can re-use the drive for the new Volume.
- *
+
  *  6. If a job wants a Volume that is in a different drive, it can either use the
  *      other drive or take the volume, only if the other drive is not in use or
  *      not reserved.
  *
- *  One nice aspect of this is that the reserve use count and the writer use count 
- *  already exist and are correctly programmed and will need no changes -- use 
+ *  One nice aspect of this is that the reserve use count and the writer use count
+ *  already exist and are correctly programmed and will need no changes -- use
  *  counts are always very tricky.
  *
- *  The old code had a concept of "reserving" a Volume, but was changed 
- *  to reserving and using a drive.  A volume is must be attached to (owned by) a 
- *  drive and can move from drive to drive or be unused given certain specific 
- *  conditions of the drive.  The key is that the drive must "own" the Volume.  
- *  The old code had the job (dcr) owning the volume (more or less).  The job was
- *  to change the insertion and removal of the volumes from the list to be based 
- *  on the drive rather than the job.  
+ *  The old code had a concept of "reserving" a Volume, but was changed
+ *  to reserving and using a drive.  A volume is must be attached to (owned by) a
+ *  drive and can move from drive to drive or be unused given certain specific
+ *  conditions of the drive.  The key is that the drive must "own" the Volume.
  *
  *  Return: VOLRES entry on success
  *          NULL volume busy on another drive
    }
    ASSERT(dev != NULL);
 
-   Dmsg2(dbglvl, "enter reserve_volume=%s drive=%s\n", VolumeName, 
+   Dmsg2(dbglvl, "enter reserve_volume=%s drive=%s\n", VolumeName,
       dcr->dev->print_name());
-   /* 
+
+   /*
     * We lock the reservations system here to ensure
     *  when adding a new volume that no newly scheduled
     *  job can reserve it.
     */
    lock_volumes();
    debug_list_volumes("begin reserve_volume");
-   /* 
+   /*
     * First, remove any old volume attached to this device as it
     *  is no longer used.
     */
       if (vol->dev) {
          Dmsg2(dbglvl, "dev=%s vol->dev=%s\n", dev->print_name(), vol->dev->print_name());
       }
-         
+
       /*
        * Check if we are trying to use the Volume on a different drive
        *  dev      is our device
    return vol;
 }
 
+/*
+ * Start walk of vol chain
+ * The proper way to walk the vol chain is:
+ *    VOLRES *vol;
+ *    foreach_vol(vol) {
+ *      ...
+ *    }
+ *    endeach_vol(vol);
+ *
+ *  It is possible to leave out the endeach_vol(vol), but
+ *   in that case, the last vol referenced must be explicitly
+ *   released with:
+ *
+ *    free_vol_item(vol);
+ *
+ */
+VOLRES *vol_walk_start()
+{
+   VOLRES *vol;
+   lock_volumes();
+   vol = (VOLRES *)vol_list->first();
+   if (vol) {
+      vol->inc_use_count();
+      Dmsg2(dbglvl, "Inc walk_start use_count=%d volname=%s\n",
+            vol->use_count(), vol->vol_name);
+   }
+   unlock_volumes();
+   return vol;
+}
+
+/*
+ * Get next vol from chain, and release current one
+ */
+VOLRES *vol_walk_next(VOLRES *prev_vol)
+{
+   VOLRES *vol;
+
+   lock_volumes();
+   vol = (VOLRES *)vol_list->next(prev_vol);
+   if (vol) {
+      vol->inc_use_count();
+      Dmsg2(dbglvl, "Inc walk_next use_count=%d volname=%s\n",
+            vol->use_count(), vol->vol_name);
+   }
+   unlock_volumes();
+   if (prev_vol) {
+      free_vol_item(prev_vol);
+   }
+   return vol;
+}
+
+/*
+ * Release last vol referenced
+ */
+void vol_walk_end(VOLRES *vol)
+{
+   if (vol) {
+      Dmsg2(dbglvl, "Free walk_end use_count=%d volname=%s\n",
+            vol->use_count(), vol->vol_name);
+      free_vol_item(vol);
+   }
+}
+
 /*
  * Search for a Volume name in the Volume list.
  *
 }
 
 
-/*  
+/*
  * Free a Volume from the Volume list if it is no longer used
  *   Note, for tape drives we want to remember where the Volume
  *   was when last used, so rather than free the volume entry,
       return false;
    }
 
-   /*  
+   /*
     * If this is a tape, we do not free the volume, rather we wait
     *  until the autoloader unloads it, or until another tape is
     *  explicitly read in this drive. This allows the SD to remember
       return true;
    } else {
       /*
-       * Note, this frees the volume reservation entry, but the 
+       * Note, this frees the volume reservation entry, but the
        *   file descriptor remains open with the OS.
        */
       return free_volume(dev);
          }
          free(vol->vol_name);
          vol->vol_name = NULL;
+         vol->destroy_mutex();
       }
       delete vol_list;
       vol_list = NULL;
          }
          free(vol->vol_name);
          vol->vol_name = NULL;
+         vol->destroy_mutex();
       }
       delete read_vol_list;
       read_vol_list = NULL;
 
 }
 
-/*  
+/*
  * Create a temporary copy of the volume list.  We do this,
  *   to avoid having the volume list locked during the
  *   call to reserve_device(), which would cause a deadlock.
  * Note, we may want to add an update counter on the vol_list
  *   so that if it is modified while we are traversing the copy
- *   we can take note and act accordingly (probably redo the 
+ *   we can take note and act accordingly (probably redo the
  *   search at least a few times).
  */
 dlist *dup_vol_list(JCR *jcr)
    dlist *temp_vol_list;
    VOLRES *vol = NULL;
 
-   lock_volumes();
-   Dmsg0(dbglvl, "lock volumes\n");                           
+   Dmsg0(dbglvl, "lock volumes\n");
 
    Dmsg0(dbglvl, "duplicate vol list\n");
    temp_vol_list = New(dlist(vol, &vol->link));
-   foreach_dlist(vol, vol_list) {
+   foreach_vol(vol) {
       VOLRES *nvol;
       VOLRES *tvol = (VOLRES *)malloc(sizeof(VOLRES));
       memset(tvol, 0, sizeof(VOLRES));
          Jmsg(jcr, M_WARNING, 0, "Logic error. Duplicating vol list hit duplicate.\n");
       }
    }
+   endeach_vol(vol);
    Dmsg0(dbglvl, "unlock volumes\n");
-   unlock_volumes();
    return temp_vol_list;
 }
 
 void free_temp_vol_list(dlist *temp_vol_list)
 {
    dlist *save_vol_list;
-   
+
    lock_volumes();
    save_vol_list = vol_list;
    vol_list = temp_vol_list;