-/*
- * Drive reservation functions for Storage Daemon
- *
- * Kern Sibbald, MM
- *
- * Split from job.c and acquire.c June 2005
- *
- * Version $Id$
- *
- */
/*
Bacula® - The Network Backup Solution
- Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
+ Copyright (C) 2000-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.
(FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
Switzerland, email:ftf@fsfeurope.org.
*/
+/*
+ * Drive reservation functions for Storage Daemon
+ *
+ * Kern Sibbald, MM
+ *
+ * Split from job.c and acquire.c June 2005
+ *
+ * Version $Id$
+ *
+ */
#include "bacula.h"
#include "stored.h"
if ((errstat=rwl_init(&reservation_lock)) != 0) {
berrno be;
Emsg1(M_ABORT, 0, _("Unable to initialize reservation lock. ERR=%s\n"),
- be.strerror(errstat));
+ be.bstrerror(errstat));
}
}
rwl_destroy(&reservation_lock);
}
+int reservations_lock_count = 0;
+
/* This applies to a drive and to Volumes */
-void lock_reservations()
+void _lock_reservations()
{
int errstat;
+ reservations_lock_count++;
if ((errstat=rwl_writelock(&reservation_lock)) != 0) {
berrno be;
Emsg2(M_ABORT, 0, "rwl_writelock failure. stat=%d: ERR=%s\n",
- errstat, be.strerror(errstat));
+ errstat, be.bstrerror(errstat));
}
}
-void unlock_reservations()
+void _unlock_reservations()
{
int errstat;
+ reservations_lock_count--;
if ((errstat=rwl_writeunlock(&reservation_lock)) != 0) {
berrno be;
Emsg2(M_ABORT, 0, "rwl_writeunlock failure. stat=%d: ERR=%s\n",
- errstat, be.strerror(errstat));
+ errstat, be.bstrerror(errstat));
+ }
+}
+
+/*
+ * List Volumes -- this should be moved to status.c
+ */
+enum {
+ debug_lock = true,
+ debug_nolock = false
+};
+
+static void debug_list_volumes(const char *imsg, bool do_lock)
+{
+ VOLRES *vol;
+ POOL_MEM msg(PM_MESSAGE);
+ int count = 0;
+ DEVICE *dev = NULL;
+
+ if (do_lock) P(vol_list_lock);
+ for (vol=(VOLRES *)vol_list->first(); vol; vol=(VOLRES *)vol_list->next(vol)) {
+ if (vol->dev) {
+ Mmsg(msg, "List from %s: %s at %p on device %s\n", imsg,
+ vol->vol_name, vol->vol_name, vol->dev->print_name());
+ } else {
+ Mmsg(msg, "List from %s: %s at %p no dev\n", imsg, vol->vol_name, vol->vol_name);
+ }
+ Dmsg1(100, "%s", msg.c_str());
+ count++;
+ }
+
+ for (vol=(VOLRES *)vol_list->first(); vol; vol=(VOLRES *)vol_list->next(vol)) {
+ if (vol->dev == dev) {
+ Dmsg0(000, "Two Volumes on same device.\n");
+ ASSERT(0);
+ dev = vol->dev;
+ }
+ }
+
+ Dmsg2(100, "List from %s: %d volumes\n", imsg, count);
+ if (do_lock) V(vol_list_lock);
+}
+
+
+/*
+ * List Volumes -- this should be moved to status.c
+ */
+void list_volumes(void sendit(const char *msg, int len, void *sarg), void *arg)
+{
+ VOLRES *vol;
+ POOL_MEM msg(PM_MESSAGE);
+ int len;
+
+ P(vol_list_lock);
+ for (vol=(VOLRES *)vol_list->first(); vol; vol=(VOLRES *)vol_list->next(vol)) {
+ if (vol->dev) {
+ len = Mmsg(msg, "%s on device %s\n", vol->vol_name, vol->dev->print_name());
+ sendit(msg.c_str(), len, arg);
+ } else {
+ len = Mmsg(msg, "%s no dev\n", vol->vol_name);
+ sendit(msg.c_str(), len, arg);
+ }
+ }
+ V(vol_list_lock);
+}
+
+/*
+ * Create a Volume item to put in the Volume list
+ * Ensure that the device points to it.
+ */
+static VOLRES *new_vol_item(DCR *dcr, const char *VolumeName)
+{
+ VOLRES *vol;
+ vol = (VOLRES *)malloc(sizeof(VOLRES));
+ memset(vol, 0, sizeof(VOLRES));
+ vol->vol_name = bstrdup(VolumeName);
+ vol->dev = dcr->dev;
+ Dmsg4(100, "New Vol=%s at %p dev=%s JobId=%u\n", VolumeName, vol->vol_name,
+ vol->dev->print_name(), (int)dcr->jcr->JobId);
+ return vol;
+}
+
+static void free_vol_item(VOLRES *vol)
+{
+ free(vol->vol_name);
+ if (vol->dev) {
+ vol->dev->vol = NULL;
}
+ free(vol);
}
* Put a new Volume entry in the Volume list. This
* effectively reserves the volume so that it will
* not be mounted again.
+ *
+ * If the device has any current volume associated with it,
+ * and it is a different Volume, and the device is not busy,
+ * we release the old Volume item and insert the new one.
+ *
+ * It is assumed that the device is free and locked so that
+ * we can change the device structure.
+ *
+ * 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"
+ * the volume (reserved, 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.
+ *
+ * 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
+ * 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
+ * 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
+ * counts are always very tricky.
+ *
+ * The old code had a concept of "reserving" a Volume, but it needs to be 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 has the job (dcr) owning the volume (more or less). The job is
+ * to change the insertion and removal of the volumes from the list to be based
+ * on the drive rather than the job. The new logic described above needs to be
+ * reviewed a couple more times for completeness and correctness. Then I can
+ * program it.
+
*
* Return: VOLRES entry on success
- * NULL if the Volume is already in the list
+ * NULL volume busy on another drive
*/
-VOLRES *new_volume(DCR *dcr, const char *VolumeName)
+VOLRES *reserve_volume(DCR *dcr, const char *VolumeName)
{
VOLRES *vol, *nvol;
+ DEVICE *dev = dcr->dev;
- Dmsg1(400, "new_volume %s\n", VolumeName);
+ ASSERT(dev != NULL);
+
+ Dmsg1(100, "reserve_volume %s\n", VolumeName);
/*
* We lock the reservations system here to ensure
* when adding a new volume that no newly scheduled
* job can reserve it.
*/
- lock_reservations();
P(vol_list_lock);
- if (dcr->dev) {
-again:
- foreach_dlist(vol, vol_list) {
- if (vol && vol->dev == dcr->dev) {
- vol_list->remove(vol);
- if (vol->vol_name) {
- free(vol->vol_name);
- }
- free(vol);
- goto again;
- }
+ debug_list_volumes("begin reserve_volume", debug_nolock);
+ /*
+ * First, remove any old volume attached to this device as it
+ * is no longer used.
+ */
+ if (dev->vol) {
+ vol = dev->vol;
+ /*
+ * Make sure we don't remove the current volume we are inserting
+ * because it was probably inserted by another job.
+ */
+ if (strcmp(vol->vol_name, VolumeName) == 0) {
+ goto get_out; /* Volume already on this device */
+ } else {
+ Dmsg3(100, "reserve_vol free vol=%s at %p JobId=%u\n", vol->vol_name,
+ vol->vol_name, (int)dcr->jcr->JobId);
+ debug_list_volumes("reserve_vol free", debug_nolock);
+ vol_list->remove(vol);
+ free_vol_item(vol);
}
}
- vol = (VOLRES *)malloc(sizeof(VOLRES));
- memset(vol, 0, sizeof(VOLRES));
- vol->vol_name = bstrdup(VolumeName);
- vol->dev = dcr->dev;
- vol->dcr = dcr;
- Dmsg2(100, "New Vol=%s dev=%s\n", VolumeName, dcr->dev->print_name());
- nvol = (VOLRES *)vol_list->binary_insert(vol, my_compare);
- if (nvol != vol) {
- free(vol->vol_name);
- free(vol);
- vol = NULL;
- if (dcr->dev) {
- DEVICE *dev = nvol->dev;
- if (!dev->is_busy()) {
- Dmsg3(100, "Swap vol=%s from dev=%s to %s\n", VolumeName,
- dev->print_name(), dcr->dev->print_name());
- nvol->dev = dcr->dev;
+
+ /* Create a new Volume entry */
+ nvol = new_vol_item(dcr, VolumeName);
+
+ /*
+ * Now try to insert the new Volume
+ */
+ vol = (VOLRES *)vol_list->binary_insert(nvol, my_compare);
+ if (vol != nvol) {
+ Dmsg2(100, "Found vol=%s dev-same=%d\n", vol->vol_name, dev==vol->dev);
+ /*
+ * At this point, a Volume with this name already is in the list,
+ * so we simply release our new Volume entry. Note, this should
+ * only happen if we are moving the volume from one drive to another.
+ */
+ Dmsg3(100, "reserve_vol free-tmp vol=%s at %p JobId=%u\n", vol->vol_name,
+ vol->vol_name, (int)dcr->jcr->JobId);
+ /*
+ * Clear dev pointer so that free_vol_item() doesn't
+ * take away our volume.
+ */
+ nvol->dev = NULL; /* don't zap dev entry */
+ free_vol_item(nvol);
+
+ /* Check if we are trying to use the Volume on a different drive */
+ if (dev != vol->dev) {
+ /* Caller wants to switch Volume to another device */
+ if (!vol->dev->is_busy()) {
+ /* OK to move it -- I'm not sure this will work */
+ Dmsg3(100, "==== Swap vol=%s from dev=%s to %s\n", VolumeName,
+ vol->dev->print_name(), dev->print_name());
+ vol->dev->vol = NULL; /* take vol from old drive */
+ vol->dev->VolHdr.VolumeName[0] = 0;
+ vol->dev = dev; /* point vol at new drive */
+ dev->vol = vol; /* point dev at vol */
dev->VolHdr.VolumeName[0] = 0;
} else {
- Dmsg3(100, "!!!! could not swap vol=%s from dev=%s to %s\n", VolumeName,
- dev->print_name(), dcr->dev->print_name());
+ Dmsg3(100, "Volume busy could not swap vol=%s from dev=%s to %s\n", VolumeName,
+ vol->dev->print_name(), dev->print_name());
+ vol = NULL; /* device busy */
}
}
}
+ dev->vol = vol;
+
+get_out:
+ debug_list_volumes("end new volume", debug_nolock);
V(vol_list_lock);
- unlock_reservations();
return vol;
}
vol.vol_name = bstrdup(VolumeName);
fvol = (VOLRES *)vol_list->binary_search(&vol, my_compare);
free(vol.vol_name);
+ Dmsg2(100, "find_vol=%s found=%d\n", VolumeName, fvol!=NULL);
+ debug_list_volumes("find_volume", debug_nolock);
V(vol_list_lock);
return fvol;
}
-/*
- * Free a Volume from the Volume list
- *
- * Returns: true if the Volume found and removed from the list
- * false if the Volume is not in the list
+/*
+ * Remove any reservation from a drive and tell the system
+ * that the volume is unused at least by us.
*/
-bool free_volume(DEVICE *dev)
+void unreserve_device(DCR *dcr)
{
- VOLRES vol, *fvol;
-
- P(vol_list_lock);
- if (dev->VolHdr.VolumeName[0] == 0) {
- Dmsg1(100, "free_volume: no vol on dev %s\n", dev->print_name());
- /*
- * Our device has no VolumeName listed, but
- * search the list for any Volume attached to
- * this device and remove it.
- */
- foreach_dlist(fvol, vol_list) {
- if (fvol && fvol->dev == dev) {
- vol_list->remove(fvol);
- if (fvol->vol_name) {
- Dmsg2(100, "free_volume %s dev=%s\n", fvol->vol_name, dev->print_name());
- free(fvol->vol_name);
- }
- free(fvol);
- break;
- }
+ DEVICE *dev = dcr->dev;
+ dev->dlock();
+ if (dcr->reserved_device) {
+ dcr->reserved_device = false;
+ dev->reserved_device--;
+ Dmsg2(100, "Dec reserve=%d dev=%s\n", dev->reserved_device, dev->print_name());
+ dcr->reserved_device = false;
+ /* If we set read mode in reserving, remove it */
+ if (dev->can_read()) {
+ dev->clear_read();
+ }
+ if (dev->num_writers < 0) {
+ Jmsg1(dcr->jcr, M_ERROR, 0, _("Hey! num_writers=%d!!!!\n"), dev->num_writers);
+ dev->num_writers = 0;
}
- goto bail_out;
- }
- Dmsg1(400, "free_volume %s\n", dev->VolHdr.VolumeName);
- vol.vol_name = bstrdup(dev->VolHdr.VolumeName);
- fvol = (VOLRES *)vol_list->binary_search(&vol, my_compare);
- if (fvol) {
- vol_list->remove(fvol);
- Dmsg2(100, "free_volume %s dev=%s\n", fvol->vol_name, dev->print_name());
- free(fvol->vol_name);
- free(fvol);
}
- free(vol.vol_name);
- dev->VolHdr.VolumeName[0] = 0;
-bail_out:
- V(vol_list_lock);
- return fvol != NULL;
+
+ volume_unused(dcr);
+ dev->dunlock();
}
-/* Free volume reserved by this dcr but not attached to a dev */
-void free_unused_volume(DCR *dcr)
+/*
+ * Free a Volume from the Volume list if it is no longer used
+ *
+ * Returns: true if the Volume found and removed from the list
+ * false if the Volume is not in the list or is in use
+ */
+bool volume_unused(DCR *dcr)
{
- VOLRES *vol;
+ DEVICE *dev = dcr->dev;
- P(vol_list_lock);
- for (vol=(VOLRES *)vol_list->first(); vol; vol=(VOLRES *)vol_list->next(vol)) {
- if (vol->dcr == dcr && (vol->dev == NULL ||
- strcmp(vol->vol_name, vol->dev->VolHdr.VolumeName) != 0)) {
- vol_list->remove(vol);
- Dmsg1(100, "free_unused_volume %s\n", vol->vol_name);
- free(vol->vol_name);
- free(vol);
- break;
- }
+ if (dev->vol == NULL) {
+ Dmsg1(100, " unreserve_volume: no vol on %s\n", dev->print_name());
+ debug_list_volumes("null return unreserve_volume", debug_lock);
+ return false;
}
- V(vol_list_lock);
+
+ if (dev->is_busy()) {
+ Dmsg1(100, "unreserve_volume: dev is busy %s\n", dev->print_name());
+ debug_list_volumes("dev busy return unreserve_volume", debug_lock);
+ return false;
+ }
+
+ return free_volume(dev);
}
/*
- * List Volumes -- this should be moved to status.c
+ * Unconditionally release the volume
*/
-void list_volumes(void sendit(const char *msg, int len, void *sarg), void *arg)
+bool free_volume(DEVICE *dev)
{
VOLRES *vol;
- char *msg;
- int len;
-
- msg = (char *)get_pool_memory(PM_MESSAGE);
- P(vol_list_lock);
- for (vol=(VOLRES *)vol_list->first(); vol; vol=(VOLRES *)vol_list->next(vol)) {
- if (vol->dev) {
- len = Mmsg(msg, "%s on device %s\n", vol->vol_name, vol->dev->print_name());
- sendit(msg, len, arg);
- } else {
- len = Mmsg(msg, "%s\n", vol->vol_name);
- sendit(msg, len, arg);
- }
+ if (dev->vol == NULL) {
+ Dmsg1(100, "No vol on dev %s\n", dev->print_name());
+ return false;
}
+ P(vol_list_lock);
+ vol = dev->vol;
+ dev->vol = NULL;
+ Dmsg1(100, "free_volume %s\n", vol->vol_name);
+ vol_list->remove(vol);
+ Dmsg3(100, "free_volume %s at %p dev=%s\n", vol->vol_name, vol->vol_name,
+ dev->print_name());
+ free_vol_item(vol);
+ debug_list_volumes("free_volume", debug_nolock);
V(vol_list_lock);
-
- free_pool_memory(msg);
+ return vol != NULL;
}
+
/* Create the Volume list */
void create_volume_list()
}
P(vol_list_lock);
for (vol=(VOLRES *)vol_list->first(); vol; vol=(VOLRES *)vol_list->next(vol)) {
- Dmsg3(000, "Unreleased Volume=%s dcr=0x%x dev=0x%x\n", vol->vol_name,
- vol->dcr, vol->dev);
+ Dmsg2(100, "Unreleased Volume=%s dev=%p\n", vol->vol_name, vol->dev);
+ free(vol->vol_name);
+ vol->vol_name = NULL;
}
delete vol_list;
vol_list = NULL;
Dmsg1(100, "Vol=%s not in use.\n", dcr->VolumeName);
return false; /* vol not in list */
}
- if (!vol->dev) { /* vol not attached to device */
- Dmsg1(100, "Vol=%s has no dev.\n", dcr->VolumeName);
- return false;
- }
+ ASSERT(vol->dev != NULL);
+
if (dcr->dev == vol->dev) { /* same device OK */
Dmsg1(100, "Vol=%s on same dev.\n", dcr->VolumeName);
return false;
store->append = append;
/* Now get all devices */
- while (bnet_recv(dir) >= 0) {
+ while (dir->recv() >= 0) {
Dmsg1(100, "<dird device: %s", dir->msg);
ok = sscanf(dir->msg, use_device, dev_name.c_str()) == 1;
if (!ok) {
unbash_spaces(dev_name);
store->device->append(bstrdup(dev_name.c_str()));
}
- } while (ok && bnet_recv(dir) >= 0);
+ } while (ok && dir->recv() >= 0);
#ifdef DEVELOPER
/* This loop is debug code and can be removed */
* Wiffle through them and find one that can do the backup.
*/
if (ok) {
- bool first = true; /* print wait message once */
+ int retries = 0; /* wait for device retries */
bool fail = false;
rctx.notify_dir = true;
lock_reservations();
}
rctx.suitable_device = false;
rctx.have_volume = false;
+ rctx.VolumeName[0] = 0;
rctx.any_drive = false;
if (!jcr->PreferMountedVols) {
/* Look for unused drives in autochangers */
}
/* Keep reservations locked *except* during wait_for_device() */
unlock_reservations();
- if (!rctx.suitable_device || !wait_for_device(jcr, first)) {
+ if (!rctx.suitable_device || !wait_for_device(jcr, retries)) {
Dmsg0(100, "Fail. !suitable_device || !wait_for_device\n");
fail = true;
}
lock_reservations();
- first = false;
bnet_sig(dir, BNET_HEARTBEAT); /* Inform Dir that we are alive */
}
unlock_reservations();
ok = true;
break;
} else if (stat == 0) { /* device busy */
- Dmsg1(110, "Suitable device found=%s, not used: busy\n", device_name);
+ Dmsg1(110, "Suitable device=%s, busy: not use\n", device_name);
} else {
/* otherwise error */
Dmsg0(110, "No suitable device found.\n");
break;
}
}
-
return ok;
}
bstrncpy(dcr->media_type, rctx.store->media_type, name_len);
bstrncpy(dcr->dev_name, rctx.device_name, name_len);
if (rctx.store->append == SD_APPEND) {
- if (rctx.exact_match && !rctx.have_volume) {
+ Dmsg2(100, "have_vol=%d vol=%s\n", rctx.have_volume, rctx.VolumeName);
+ if (!rctx.have_volume) {
dcr->any_volume = true;
if (dir_find_next_appendable_volume(dcr)) {
bstrncpy(rctx.VolumeName, dcr->VolumeName, sizeof(rctx.VolumeName));
- Dmsg2(100, "JobId=%u looking for Volume=%s\n", rctx.jcr->JobId, rctx.VolumeName);
+ Dmsg2(100, "JobId=%u looking for Volume=%s\n", (int)rctx.jcr->JobId, rctx.VolumeName);
rctx.have_volume = true;
} else {
Dmsg0(100, "No next volume found\n");
+ rctx.have_volume = false;
rctx.VolumeName[0] = 0;
}
}
}
}
if (!ok) {
+ rctx.have_volume = false;
free_dcr(dcr);
Dmsg0(110, "Not OK.\n");
return 0;
ASSERT(dcr);
- /* Get locks in correct order */
- unlock_reservations();
- P(dev->mutex);
- lock_reservations();
+ dev->dlock();
if (is_device_unmounted(dev)) {
Dmsg1(200, "Device %s is BLOCKED due to user unmount.\n", dev->print_name());
dcr->reserved_device = true;
bail_out:
- V(dev->mutex);
+ dev->dunlock();
return ok;
}
ASSERT(dcr);
- /* Get locks in correct order */
- unlock_reservations();
- P(dev->mutex);
- lock_reservations();
+ dev->dlock();
/* If device is being read, we cannot write it */
if (dev->can_read()) {
ok = true;
bail_out:
- V(dev->mutex);
+ dev->dunlock();
return ok;
}
}
/* Check for prefer mounted volumes */
- if (rctx.PreferMountedVols && !dev->VolHdr.VolumeName[0] && dev->is_tape()) {
+// if (rctx.PreferMountedVols && !dev->VolHdr.VolumeName[0] && dev->is_tape()) {
+ if (rctx.PreferMountedVols && !dev->vol && dev->is_tape()) {
Mmsg(jcr->errmsg, _("3606 JobId=%u prefers mounted drives, but drive %s has no Volume.\n"),
jcr->JobId, dev->print_name());
queue_reserve_message(jcr);
- Dmsg1(110, "failed: want mounted -- no vol JobId=%u\n", jcr->JobId);
+ Dmsg1(110, "failed: want mounted -- no vol JobId=%u\n", (uint32_t)jcr->JobId);
return 0; /* No volume mounted */
}
/* Check for exact Volume name match */
+ /* ***FIXME*** use dev->vol.VolumeName */
if (rctx.exact_match && rctx.have_volume &&
strcmp(dev->VolHdr.VolumeName, rctx.VolumeName) != 0) {
Mmsg(jcr->errmsg, _("3607 JobId=%u wants Vol=\"%s\" drive has Vol=\"%s\" on drive %s.\n"),
}
/* Check for unused autochanger drive */
+ /* ***FIXME*** use !dev->is_busy() */
if (rctx.autochanger_only && dev->num_writers == 0 &&
dev->VolHdr.VolumeName[0] == 0) {
/* Device is available but not yet reserved, reserve it for us */
return 1;
} else {
/* Drive Pool not suitable for us */
- Mmsg(jcr->errmsg, _("3608 JobId=%u wants Pool=\"%s\" but have Pool=\"%s\" on drive %s.\n"),
- jcr->JobId, dcr->pool_name, dev->pool_name, dev->print_name());
+ Mmsg(jcr->errmsg, _(
+"3608 JobId=%u wants Pool=\"%s\" but have Pool=\"%s\" nreserve=%d on drive %s.\n"),
+ jcr->JobId, dcr->pool_name, dev->pool_name,
+ dev->reserved_device, dev->print_name());
queue_reserve_message(jcr);
Dmsg2(110, "failed: busy num_writers=0, reserved, pool=%s wanted=%s\n",
dev->pool_name, dcr->pool_name);