/*
- Bacula® - The Network Backup Solution
-
- 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.
- This program is Free Software; you can redistribute it and/or
- 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.
-
- 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 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 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.
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2015 Kern Sibbald
+ Copyright (C) 2000-2014 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.
*/
/*
* Volume management functions for Storage Daemon
/*
* For append volumes the key is the VolumeName.
*/
-static int my_compare(void *item1, void *item2)
+static int name_compare(void *item1, void *item2)
{
return strcmp(((VOLRES *)item1)->vol_name, ((VOLRES *)item2)->vol_name);
}
return 1;
}
-
-bool is_vol_list_empty()
+bool is_vol_list_empty()
{
return vol_list->empty();
}
}
}
-void lock_read_volumes(const char *file="**Unknown", int line=0)
+#define lock_read_volumes() lock_read_volumes_p(__FILE__, __LINE__)
+static void lock_read_volumes_p(const char *file="**Unknown", int line=0)
{
bthread_mutex_lock_p(&read_vol_lock, file, line);
}
-void unlock_read_volumes()
+static void unlock_read_volumes()
{
bthread_mutex_unlock(&read_vol_lock);
}
* Note, we use VOLRES because it simplifies the code
* even though, the only part of VOLRES that we need is
* the volume name. The same volume may be in the list
- * multiple times, but each one is distinguished by the
+ * multiple times, but each one is distinguished by the
* JobId. We use JobId, VolumeName as the key.
* We can get called multiple times for the same volume because
* when parsing the bsr, the volume name appears multiple times.
nvol = new_vol_item(NULL, VolumeName);
nvol->set_jobid(jcr->JobId);
+ nvol->set_reading();
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();
+}
+
+/*
+ * Check if volume name is in the read list.
+ */
+bool is_read_volume(JCR *jcr, const char *VolumeName)
+{
+ VOLRES vol, *fvol;
+ lock_read_volumes();
+ vol.vol_name = bstrdup(VolumeName);
+ fvol = (VOLRES *)read_vol_list->binary_search(&vol, name_compare);
+ free(vol.vol_name);
+ unlock_read_volumes();
+ return fvol != NULL;
}
/*
VOLRES *vol;
POOL_MEM msg(PM_MESSAGE);
+ if (debug_level < dbglvl) {
+ return;
+ }
+
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());
+ Mmsg(msg, "List %s: %s in_use=%d swap=%d on %s device %s\n", imsg,
+ vol->vol_name, vol->is_in_use(), vol->is_swapping(),
+ vol->dev->print_type(), vol->dev->print_name());
} else {
- Mmsg(msg, "List %s: %s in_use=%d swap=%d no dev\n", imsg, vol->vol_name,
+ Mmsg(msg, "List %s: %s in_use=%d swap=%d no dev\n", imsg, vol->vol_name,
vol->is_in_use(), vol->is_swapping());
}
Dmsg1(dbglvl, "%s", msg.c_str());
foreach_vol(vol) {
DEVICE *dev = vol->dev;
if (dev) {
- len = Mmsg(msg, "%s on device %s\n", vol->vol_name, dev->print_name());
+ len = Mmsg(msg, "Reserved volume: %s on %s device %s\n", vol->vol_name,
+ dev->print_type(), dev->print_name());
sendit(msg.c_str(), len, arg);
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, "Volume %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);
}
foreach_dlist(vol, read_vol_list) {
DEVICE *dev = vol->dev;
if (dev) {
- len = Mmsg(msg, "Read volume: %s on device %s\n", vol->vol_name, dev->print_name());
+ len = Mmsg(msg, "Read volume: %s on %s device %s\n", vol->vol_name,
+ dev->print_type(), 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,
+ len = Mmsg(msg, "Volume: %s no device. volinuse=%d\n", vol->vol_name,
vol->is_in_use());
sendit(msg.c_str(), len, arg);
}
DEVICE *dev = NULL;
vol->dec_use_count();
- vol->Lock();
+ vol->vLock();
if (vol->use_count() > 0) {
- vol->Unlock();
+ vol->vUnlock();
return;
}
- vol->Unlock();
+ vol->vUnlock();
free(vol->vol_name);
if (vol->dev) {
dev = vol->dev;
* 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.
*
*
* Return: VOLRES entry on success
* NULL volume busy on another drive
+ * jcr->errmsg has details
*/
VOLRES *reserve_volume(DCR *dcr, const char *VolumeName)
{
VOLRES *vol, *nvol;
DEVICE * volatile dev = dcr->dev;
+ JCR *jcr = dcr->jcr;
+ jcr->errmsg[0] = 0;
if (job_canceled(dcr->jcr)) {
+ Mmsg1(jcr->errmsg, _("Could not reserve volume \"%s\", because job canceled.\n"),
+ dev->VolHdr.VolumeName);
return NULL;
}
- ASSERT(dev != NULL);
+ ASSERT2(dev != NULL, "No device in reserve_volume!");
Dmsg2(dbglvl, "enter reserve_volume=%s drive=%s\n", VolumeName,
dcr->dev->print_name());
+ /* If acquiring to write, don't accept a Volume in read list */
+ if (dcr->is_writing() && is_read_volume(dcr->jcr, VolumeName)) {
+ Mmsg1(jcr->errmsg, _("Could not reserve volume \"%s\" for append, because it will be read.\n"),
+ dev->VolHdr.VolumeName);
+ return NULL;
+ }
+
/*
* We lock the reservations system here to ensure
* when adding a new volume that no newly scheduled
* is not being used and is marked as not reserved.
*/
if (strcmp(vol->vol_name, VolumeName) == 0) {
- Dmsg2(dbglvl, "=== set reserved vol=%s dev=%s\n", VolumeName,
+ Dmsg2(dbglvl, "set reserved vol=%s dev=%s\n", VolumeName,
vol->dev->print_name());
goto get_out; /* Volume already on this device */
} else {
/* Don't release a volume if it was reserved by someone other than us */
- if (vol->is_in_use() && !dcr->reserved_volume) {
- Dmsg1(dbglvl, "Cannot free vol=%s. It is reserved.\n", vol->vol_name);
+ if (vol->is_in_use() && !dcr->reserved_volume) {
+ Dmsg2(dbglvl, "Set wait(). Cannot free vol=%s for %s. It is reserved.\n", vol->vol_name, VolumeName);
+ Mmsg1(dcr->jcr->errmsg, _("Cannot free Volume \"%s\", because it is reserved by someone else.\n"),
+ vol->vol_name);
+ dev->set_wait();
vol = NULL; /* vol in use */
goto get_out;
}
nvol = new_vol_item(dcr, VolumeName);
/*
- * Now try to insert the new Volume
+ * Handle request for read volume for file
+ * device, for which we assume we can open multiple
+ * devices to read the Volume.
+ *
+ * Note: when doing multiple simultaneous reads
+ * of the same volume, the volume names are not
+ * inserted into the write volume list.
+ */
+ if (dcr->is_reading() && dev->is_file()) {
+ nvol->set_jobid(dcr->jcr->JobId);
+ nvol->set_reading();
+ vol = nvol;
+ dev->vol = vol;
+ goto get_out;
+ } else {
+ vol = (VOLRES *)vol_list->binary_insert(nvol, name_compare);
+ }
+
+ /*
+ * This part handles any write volumes or read volumes that
+ * cannot be simultaneously on multiple devices.
*/
- vol = (VOLRES *)vol_list->binary_insert(nvol, my_compare);
if (vol != nvol) {
- Dmsg2(dbglvl, "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.
*/
- Dmsg2(dbglvl, "reserve_vol free-tmp vol=%s at %p\n",
+ Dmsg2(dbglvl, "Found vol=%s dev-same=%d\n", vol->vol_name, dev==vol->dev);
+ Dmsg2(dbglvl, "reserve_vol free-tmp vol=%s at %p\n",
vol->vol_name, vol->vol_name);
/*
- * Clear dev pointer so that free_vol_item() doesn't
- * take away our volume.
+ * 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);
/* Caller wants to switch Volume to another device */
if (!vol->dev->is_busy() && !vol->is_swapping()) {
int32_t slot;
- Dmsg3(dbglvl, "==== Swap vol=%s from dev=%s to %s\n",
+ Dmsg3(dbglvl, "==== Swap vol=%s from dev=%s to %s\n",
VolumeName, vol->dev->print_name(), dev->print_name());
free_volume(dev); /* free any volume attached to our drive */
Dmsg1(50, "set_unload dev=%s\n", dev->print_name());
vol->dev = dev; /* point the Volume at our drive */
dev->vol = vol; /* point our drive at the Volume */
} else {
- Jmsg7(dcr->jcr, M_WARNING, 0, "Need volume from other drive, "
- "but swap not possible. Status: read=%d num_writers=%d "
- "num_reserve=%d swap=%d vol=%s from dev=%s to %s\n",
- vol->dev->can_read(), vol->dev->num_writers,
- vol->dev->num_reserved(), vol->is_swapping(),
- VolumeName, vol->dev->print_name(), dev->print_name());
- if (vol->is_swapping() && dev->swap_dev) {
- Dmsg3(dbglvl, "Swap failed vol=%s from=%s to dev=%s\n",
- vol->vol_name, dev->swap_dev->print_name(), dev->print_name());
+ if (dev) {
+ Jmsg8(jcr, M_WARNING, 0, "Need volume for %s from other drive, "
+ "but swap not possible. Status: reader=%d writers=%d "
+ "reserves=%d swap=%d vol=%s from dev=%s to %s\n",
+ dcr->is_writing()?"write":"read",
+ vol->dev->can_read(), vol->dev->num_writers,
+ vol->dev->num_reserved(), vol->is_swapping(),
+ VolumeName, vol->dev->print_name(), dev->print_name());
+ }
+ if (vol->is_swapping()) {
+ DEVICE *swapdev = dev->swap_dev;
+ if (vol && dev && swapdev) {
+ Mmsg3(jcr->errmsg, _("Volume %s is busy swapping from %s to %s\n"),
+ NPRT(vol->vol_name), dev->print_name(), swapdev->print_name());
+ } else {
+ Mmsg1(jcr->errmsg, _("Volume %s is busy swapping.\n"),
+ NPRT(vol->vol_name));
+ }
+ } else if (vol->dev) {
+ Mmsg2(jcr->errmsg, _("%s device %s is busy.\n"),
+ vol->dev->print_type(), vol->dev->print_name());
} else {
- Dmsg3(dbglvl, "Swap failed vol=%s from=%p to dev=%s\n",
- vol->vol_name, dev->swap_dev, dev->print_name());
+ Mmsg1(jcr->errmsg, _("Volume %s is busy swapping.\n"),
+ NPRT(vol->vol_name));
}
debug_list_volumes("failed swap");
vol = NULL; /* device busy */
get_out:
if (vol) {
- Dmsg2(dbglvl, "=== set in_use. vol=%s dev=%s\n", vol->vol_name,
+ Dmsg2(dbglvl, "set in_use. vol=%s dev=%s\n", vol->vol_name,
vol->dev->print_name());
vol->set_in_use();
dcr->reserved_volume = true;
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);
}
+ unlock_volumes();
return vol;
}
void vol_walk_end(VOLRES *vol)
{
if (vol) {
+ lock_volumes();
Dmsg2(dbglvl, "Free walk_end use_count=%d volname=%s\n",
vol->use_count(), vol->vol_name);
free_vol_item(vol);
+ unlock_volumes();
}
}
* Returns: VOLRES entry on success
* NULL if the Volume is not in the list
*/
-VOLRES *find_volume(const char *VolumeName)
+static VOLRES *find_volume(const char *VolumeName)
{
VOLRES vol, *fvol;
/* Do not lock reservations here */
lock_volumes();
vol.vol_name = bstrdup(VolumeName);
- fvol = (VOLRES *)vol_list->binary_search(&vol, my_compare);
+ fvol = (VOLRES *)vol_list->binary_search(&vol, name_compare);
free(vol.vol_name);
Dmsg2(dbglvl, "find_vol=%s found=%d\n", VolumeName, fvol!=NULL);
debug_list_volumes("find_volume");
* Returns: VOLRES entry on success
* NULL if the Volume is not in the list
*/
-static VOLRES *find_read_volume(const char *VolumeName)
+static VOLRES *find_read_volume(const char *VolumeName)
{
VOLRES vol, *fvol;
/* Do not lock reservations here */
lock_read_volumes();
vol.vol_name = bstrdup(VolumeName);
- /* Note, we do want a simple my_compare on volume name only here */
- fvol = (VOLRES *)read_vol_list->binary_search(&vol, my_compare);
+ /* Note, we do want a simple name_compare on volume name only here */
+ fvol = (VOLRES *)read_vol_list->binary_search(&vol, name_compare);
free(vol.vol_name);
Dmsg2(dbglvl, "find_read_vol=%s found=%d\n", VolumeName, fvol!=NULL);
unlock_read_volumes();
return false;
}
- Dmsg1(dbglvl, "=== clear in_use vol=%s\n", dev->vol->vol_name);
+ Dmsg1(dbglvl, "Clear in_use vol=%s\n", dev->vol->vol_name);
dev->vol->clear_in_use();
if (dev->vol->is_swapping()) {
* explicitly read in this drive. This allows the SD to remember
* where the tapes are or last were.
*/
- Dmsg4(dbglvl, "=== set not reserved vol=%s num_writers=%d dev_reserved=%d dev=%s\n",
+ Dmsg4(dbglvl, "set not reserved vol=%s writers=%d reserves=%d dev=%s\n",
dev->vol->vol_name, dev->num_writers, dev->num_reserved(), dev->print_name());
if (dev->is_tape() || dev->is_autochanger()) {
return true;
/*
* Unconditionally release the volume entry
+ * Note: read volumes are not in the list, so
+ * do not attempt to remove them.
*/
bool free_volume(DEVICE *dev)
{
}
/* Don't free a volume while it is being swapped */
if (!vol->is_swapping()) {
- Dmsg1(dbglvl, "=== clear in_use vol=%s\n", vol->vol_name);
+ Dmsg1(dbglvl, "Clear in_use vol=%s\n", vol->vol_name);
dev->vol = NULL;
- vol_list->remove(vol);
- Dmsg2(dbglvl, "=== remove volume %s dev=%s\n", vol->vol_name, dev->print_name());
+ if (vol->is_writing()) {
+ vol_list->remove(vol);
+ }
+ Dmsg2(dbglvl, "Remove volume %s dev=%s\n", vol->vol_name, dev->print_name());
free_vol_item(vol);
debug_list_volumes("free_volume");
} else {
- Dmsg1(dbglvl, "=== cannot clear swapping vol=%s\n", vol->vol_name);
+ Dmsg1(dbglvl, "=== Cannot clear. Swapping vol=%s\n", vol->vol_name);
}
unlock_volumes();
-// pthread_cond_broadcast(&wait_next_vol);
return true;
}
-
+
/* Create the Volume list */
void create_volume_lists()
{
}
}
-/*
- * Determine if caller can write on volume
+/*
+ * Determine if caller can write on volume.
+ * If not, return reason in jcr->errmsg
*/
bool DCR::can_i_write_volume()
{
vol = find_read_volume(VolumeName);
if (vol) {
+ Mmsg(jcr->errmsg, "Found in read list; cannot write vol=%s\n", VolumeName);
Dmsg1(100, "Found in read list; cannot write vol=%s\n", VolumeName);
return false;
}
}
/*
- * Determine if caller can read or write volume
+ * Determine if caller can read or write volume.
+ * If not, return reason in jcr->errmsg
*/
bool DCR::can_i_use_volume()
{
VOLRES *vol;
if (job_canceled(jcr)) {
+ Mmsg(jcr->errmsg, "Job is canceled\n");
return false;
}
lock_volumes();
Dmsg1(dbglvl, "Vol=%s not in use.\n", VolumeName);
goto get_out; /* vol not in list */
}
- ASSERT(vol->dev != NULL);
+ ASSERT2(vol->dev != NULL, "No device in can_i_use_volume!");
if (dev == vol->dev) { /* same device OK */
Dmsg1(dbglvl, "Vol=%s on same dev.\n", VolumeName);
} else {
Dmsg2(dbglvl, "Vol=%s dev=%s busy.\n", VolumeName, vol->dev->print_name());
}
- Dmsg2(dbglvl, "Vol=%s in use by %s.\n", VolumeName, vol->dev->print_name());
+ Mmsg(jcr->errmsg, "Volume=%s in use on another device %s.\n", VolumeName, vol->dev->print_name());
+ Dmsg2(dbglvl, "Volume=%s in use on another device %s.\n", VolumeName, vol->dev->print_name());
rtn = false;
get_out:
memset(tvol, 0, sizeof(VOLRES));
tvol->vol_name = bstrdup(vol->vol_name);
tvol->dev = vol->dev;
- nvol = (VOLRES *)temp_vol_list->binary_insert(tvol, my_compare);
+ nvol = (VOLRES *)temp_vol_list->binary_insert(tvol, name_compare);
if (tvol != nvol) {
tvol->dev = NULL; /* don't zap dev entry */
free_vol_item(tvol);