#include "bacula.h" /* pull in global headers */
#include "stored.h" /* pull in Storage Deamon headers */
+static int can_reserve_drive(DCR *dcr);
+
/*
* Create a new Device Control Record and attach
* it to the device (if this is a real job).
dcr->max_spool_size = dev->device->max_spool_size;
/* Attach this dcr only if dev is initialized */
if (dev->fd != 0 && jcr && jcr->JobType != JT_SYSTEM) {
- dev->attached_dcrs->append(dcr);
-// jcr->dcrs->append(dcr);
+ dev->attached_dcrs->append(dcr); /* attach dcr to device */
+// jcr->dcrs->append(dcr); /* put dcr in list for Job */
}
return dcr;
}
/* Detach this dcr only if the dev is initialized */
if (dev->fd != 0 && jcr && jcr->JobType != JT_SYSTEM) {
- dev->attached_dcrs->remove(dcr);
-// remove_dcr_from_dcrs(dcr);
+ dev->attached_dcrs->remove(dcr); /* detach dcr from device */
+// remove_dcr_from_dcrs(dcr); /* remove dcr from jcr list */
}
if (dcr->block) {
free_block(dcr->block);
* starting the job. If the device is not available, the DIR
* can wait (to be implemented 1/05).
*/
-bool reserve_device_for_read(JCR *jcr, DEVICE *dev)
+bool reserve_device_for_read(DCR *dcr)
{
- DCR *dcr = jcr->dcr;
+ DEVICE *dev = dcr->dev;
+ JCR *jcr = dcr->jcr;
bool first;
ASSERT(dcr);
* Returns: NULL if failed for any reason
* dcr if successful
*/
-DCR *acquire_device_for_read(JCR *jcr, DEVICE *dev)
+DCR *acquire_device_for_read(DCR *dcr)
{
+ DEVICE *dev = dcr->dev;
+ JCR *jcr = dcr->jcr;
bool vol_ok = false;
bool tape_previously_mounted;
bool tape_initially_mounted;
VOL_LIST *vol;
bool try_autochanger = true;
int i;
- DCR *dcr = jcr->dcr;
int vol_label_status;
dev->block(BST_DOING_ACQUIRE);
- init_device_wait_timers(dcr);
-
- tape_previously_mounted = dev->can_read() ||
- dev->can_append() ||
- dev->is_labeled();
- tape_initially_mounted = tape_previously_mounted;
-
if (dev->num_writers > 0) {
Jmsg2(jcr, M_FATAL, 0, _("Num_writers=%d not zero. Job %d canceled.\n"),
dev->num_writers, jcr->JobId);
for (i=1; i<jcr->CurVolume; i++) {
vol = vol->next;
}
+ if (!vol) {
+ goto get_out; /* should not happen */
+ }
bstrncpy(dcr->VolumeName, vol->VolumeName, sizeof(dcr->VolumeName));
+ init_device_wait_timers(dcr);
+
+ tape_previously_mounted = dev->can_read() ||
+ dev->can_append() ||
+ dev->is_labeled();
+ tape_initially_mounted = tape_previously_mounted;
+
+
/* Volume info is always needed because of VolParts */
Dmsg0(200, "dir_get_volume_info\n");
if (!dir_get_volume_info(dcr, GET_VOL_INFO_FOR_READ)) {
* the first tor reserve the device, we put the pool
* name and pool type in the device record.
*/
-bool reserve_device_for_append(JCR *jcr, DEVICE *dev)
+bool reserve_device_for_append(DCR *dcr)
{
- DCR *dcr = jcr->dcr;
+ JCR *jcr = dcr->jcr;
+ DEVICE *dev = dcr->dev;
bool ok = false;
+ bool first;
ASSERT(dcr);
-
dev->block(BST_DOING_ACQUIRE);
- if (dev->can_read()) {
- Jmsg(jcr, M_WARNING, 0, _("Device %s is busy reading.\n"), dev->print_name());
- goto bail_out;
+
+ Mmsg2(jcr->errmsg, _("Device %s is busy reading. Job %d canceled.\n"),
+ dev->print_name(), jcr->JobId);
+ for (first=true; dev->can_read(); first=false) {
+ dev->unblock();
+ if (!wait_for_device(dcr, jcr->errmsg, first)) {
+ return false;
+ }
+ dev->block(BST_DOING_ACQUIRE);
}
- if (device_is_unmounted(dev)) {
- Jmsg(jcr, M_WARNING, 0, _("device %s is BLOCKED due to user unmount.\n"),
- dev->print_name());
- goto bail_out;
+
+
+ Mmsg(jcr->errmsg, _("Device %s is BLOCKED due to user unmount.\n"),
+ dev->print_name());
+ for (first=true; device_is_unmounted(dev); first=false) {
+ dev->unblock();
+ if (!wait_for_device(dcr, jcr->errmsg, first)) {
+ return false;
+ }
+ dev->block(BST_DOING_ACQUIRE);
}
+
Dmsg1(190, "reserve_append device is %s\n", dev_is_tape(dev)?"tape":"disk");
+
+ for ( ;; ) {
+ switch (can_reserve_drive(dcr)) {
+ case 0:
+ /* ****FIXME**** Make wait */
+ goto bail_out;
+ case -1:
+ goto bail_out; /* error */
+ default:
+ break; /* OK, reserve drive */
+ }
+ break;
+ }
+
+
+ dev->reserved_device++;
+ dcr->reserved_device = true;
+ ok = true;
+
+bail_out:
+ dev->unblock();
+ return ok;
+}
+
+/*
+ * Returns: 1 if drive can be reserved
+ * 0 if we should wait
+ * -1 on error
+ */
+static int can_reserve_drive(DCR *dcr)
+{
+ DEVICE *dev = dcr->dev;
+ JCR *jcr = dcr->jcr;
/*
* First handle the case that the drive is not yet in append mode
*/
} else {
/* Drive not suitable for us */
Jmsg(jcr, M_WARNING, 0, _("Device %s is busy writing on another Volume.\n"), dev->print_name());
- goto bail_out;
+ return 0; /* wait */
}
} else {
/* Device is available but not yet reserved, reserve it for us */
bstrncpy(dev->pool_type, dcr->pool_type, sizeof(dev->pool_type));
dev->PoolId = dcr->PoolId;
}
- goto do_reserve;
+ return 1; /* reserve drive */
}
/*
} else {
/* Drive not suitable for us */
Jmsg(jcr, M_WARNING, 0, _("Device %s is busy writing on another Volume.\n"), dev->print_name());
- goto bail_out;
+ return 0; /* wait */
}
} else {
Pmsg0(000, "Logic error!!!! Should not get here.\n");
- goto bail_out; /* should not get here */
+ Jmsg0(jcr, M_FATAL, 0, _("Logic error!!!! Should not get here.\n"));
+ return -1; /* error, should not get here */
}
-
-do_reserve:
- dev->reserved_device++;
- dcr->reserved_device = true;
- ok = true;
-
-bail_out:
- dev->unblock();
- return ok;
+ return 1; /* reserve drive */
}
/*
* Note, normally reserve_device_for_append() is called
* before this routine.
*/
-DCR *acquire_device_for_append(JCR *jcr, DEVICE *dev)
+DCR *acquire_device_for_append(DCR *dcr)
{
bool release = false;
bool recycle = false;
bool do_mount = false;
- DCR *dcr = jcr->dcr;
+ DEVICE *dev = dcr->dev;
+ JCR *jcr = dcr->jcr;
- if (!dcr) {
- dcr = new_dcr(jcr, dev);
- }
dev->block(BST_DOING_ACQUIRE);
Dmsg1(190, "acquire_append device is %s\n", dev_is_tape(dev)?"tape":"disk");
return false;
}
- if (!acquire_device_for_append(jcr, dev)) {
+ if (!acquire_device_for_append(dcr)) {
set_jcr_job_status(jcr, JS_ErrorTerminated);
return false;
}
exit(1);
}
unlock_device(out_dev);
- if (!acquire_device_for_append(out_jcr, out_dev)) {
+ if (!acquire_device_for_append(out_jcr->dcr)) {
free_jcr(in_jcr);
exit(1);
}
}
}
Pmsg1(000, "open_dev %s OK\n", dev->print_name());
- dev->state |= ST_APPEND;
+ dev->set_append(); /* put volume in append mode */
unlock_device(dev);
free_block(block);
return true;
* subroutine.
*/
Dmsg0(100, "just before acquire_device\n");
- if (!acquire_device_for_append(jcr, dev)) {
+ if (!acquire_device_for_append(dcr)) {
set_jcr_job_status(jcr, JS_ErrorTerminated);
return;
}
close_dev(dev);
dev->state &= ~(ST_READ|ST_APPEND);
dev->num_writers = 0;
- if (!acquire_device_for_read(jcr, dev)) {
+ if (!acquire_device_for_read(dcr)) {
Pmsg1(-1, "%s", dev->errmsg);
goto bail_out;
}
get_cmd(_("Mount second tape. Press enter when ready: "));
}
- dev->state &= ~ST_READ;
- if (!acquire_device_for_read(jcr, dev)) {
+ dev->clear_read();
+ if (!acquire_device_for_read(dcr)) {
Pmsg1(-1, "%s", dev->errmsg);
goto bail_out;
}
jcr->bsr = NULL;
create_vol_list(jcr);
close_dev(dev);
- dev->state &= ~ST_READ;
- if (!acquire_device_for_read(jcr, dev)) {
+ dev->clear_read();
+ if (!acquire_device_for_read(dcr)) {
Pmsg2(0, "Cannot open Dev=%s, Vol=%s\n", dev->print_name(), dcr->VolumeName);
return false;
}
create_vol_list(jcr);
if (mode) { /* read only access? */
- if (!acquire_device_for_read(jcr, dev)) {
+ if (!acquire_device_for_read(dcr)) {
return NULL;
}
}
steal_device_lock(dev, &hold, BST_DOING_ACQUIRE);
dcr->VolumeName[0] = 0;
- dev->state &= ~ST_LABEL; /* force read of label */
+ dev->clear_labeled(); /* force read of label */
switch (read_dev_volume_label(dcr)) {
case VOL_OK:
bnet_fsend(dir, _("3001 Mounted Volume: %s\n"), dev->VolHdr.VolName);
goto bail_out; /* error */
}
- dev->state &= ~ST_LABEL; /* force read of label */
+ dev->clear_labeled(); /* force read of label */
switch (read_dev_volume_label(dcr)) {
case VOL_OK:
/* DO NOT add quotes around the Volume name. It is scanned in the DIR */
dcr->PoolId = PoolId;
jcr->dcr = dcr;
if (append == SD_APPEND) {
- ok = reserve_device_for_append(jcr, device->dev);
+ ok = reserve_device_for_append(dcr);
} else {
- ok = reserve_device_for_read(jcr, device->dev);
+ ok = reserve_device_for_read(dcr);
}
if (!ok) {
bnet_fsend(dir, _("3927 Could not reserve device: %s\n"), dev_name.c_str());
bstrncpy(dcr->dev_name, dev_name, name_len);
jcr->dcr = dcr;
if (append == SD_APPEND) {
- ok = reserve_device_for_append(jcr, device->dev);
+ ok = reserve_device_for_append(dcr);
} else {
- ok = reserve_device_for_read(jcr, device->dev);
+ ok = reserve_device_for_read(dcr);
}
if (!ok) {
Jmsg(jcr, M_WARNING, 0, _("Could not reserve device: %s\n"), dev_name.c_str());
if (jcr->NumVolumes > 1 && jcr->CurVolume < jcr->NumVolumes) {
close_dev(dev);
dev->clear_read();
- if (!acquire_device_for_read(jcr, dev)) {
+ if (!acquire_device_for_read(dcr)) {
Jmsg2(jcr, M_FATAL, 0, "Cannot open Dev=%s, Vol=%s\n", dev->print_name(),
dcr->VolumeName);
return false;
uint32_t new_VolSessionId();
/* From acquire.c */
-bool reserve_device_for_append(JCR *jcr, DEVICE *dev);
-DCR *acquire_device_for_append(JCR *jcr, DEVICE *dev);
-bool reserve_device_for_read(JCR *jcr, DEVICE *dev);
-DCR *acquire_device_for_read(JCR *jcr, DEVICE *dev);
+bool reserve_device_for_append(DCR *dcr);
+DCR *acquire_device_for_append(DCR *dcr);
+bool reserve_device_for_read(DCR *dcr);
+DCR *acquire_device_for_read(DCR *dcr);
bool release_device(DCR *dcr);
DCR *new_dcr(JCR *jcr, DEVICE *dev);
void free_dcr(DCR *dcr);
jcr->VolList->VolumeName);
/* Ready device for reading */
- if (!acquire_device_for_read(jcr, dcr->dev)) {
+ if (!acquire_device_for_read(dcr)) {
free_vol_list(jcr);
bnet_fsend(fd, FD_error);
return false;
--- /dev/null
+/*
+ * Subroutines to handle waiting for operator intervention
+ * or waiting for a Device to be released
+ *
+ * Code for wait_for_sysop() pulled from askdir.c
+ *
+ * Kern Sibbald, March 2005
+ *
+ * Version $Id$
+ */
+/*
+ Copyright (C) 2000-2005 Kern Sibbald
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version.
+
+ 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., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ */
+
+#include "bacula.h" /* pull in global headers */
+#include "stored.h" /* pull in Storage Deamon headers */
+
+static bool double_jcr_wait_time(JCR *jcr);
+
+
+/*
+ * Wait for SysOp to mount a tape on a specific device
+ *
+ * Returns: status from pthread_cond_timedwait()
+ */
+int wait_for_sysop(DCR *dcr)
+{
+ struct timeval tv;
+ struct timezone tz;
+ struct timespec timeout;
+ time_t last_heartbeat = 0;
+ time_t first_start = time(NULL);
+ int stat = 0;
+ int add_wait;
+ bool unmounted;
+ DEVICE *dev = dcr->dev;
+ JCR *jcr = dcr->jcr;
+
+ P(dev->mutex);
+ unmounted = (dev->dev_blocked == BST_UNMOUNTED) ||
+ (dev->dev_blocked == BST_UNMOUNTED_WAITING_FOR_SYSOP);
+
+ dev->poll = false;
+ /*
+ * Wait requested time (dev->rem_wait_sec). However, we also wake up every
+ * HB_TIME seconds and send a heartbeat to the FD and the Director
+ * to keep stateful firewalls from closing them down while waiting
+ * for the operator.
+ */
+ add_wait = dev->rem_wait_sec;
+ if (me->heartbeat_interval && add_wait > me->heartbeat_interval) {
+ add_wait = me->heartbeat_interval;
+ }
+ /* If the user did not unmount the tape and we are polling, ensure
+ * that we poll at the correct interval.
+ */
+ if (!unmounted && dev->vol_poll_interval && add_wait > dev->vol_poll_interval) {
+ add_wait = dev->vol_poll_interval;
+ }
+
+ if (!unmounted) {
+ dev->dev_prev_blocked = dev->dev_blocked;
+ dev->dev_blocked = BST_WAITING_FOR_SYSOP; /* indicate waiting for mount */
+ }
+
+ for ( ; !job_canceled(jcr); ) {
+ time_t now, start;
+
+ gettimeofday(&tv, &tz);
+ timeout.tv_nsec = tv.tv_usec * 1000;
+ timeout.tv_sec = tv.tv_sec + add_wait;
+
+ Dmsg3(400, "I'm going to sleep on device %s. HB=%d wait=%d\n", dev->print_name(),
+ (int)me->heartbeat_interval, dev->wait_sec);
+ start = time(NULL);
+ /* Wait required time */
+ stat = pthread_cond_timedwait(&dev->wait_next_vol, &dev->mutex, &timeout);
+ Dmsg1(400, "Wokeup from sleep on device stat=%d\n", stat);
+
+ now = time(NULL);
+ dev->rem_wait_sec -= (now - start);
+
+ /* Note, this always triggers the first time. We want that. */
+ if (me->heartbeat_interval) {
+ if (now - last_heartbeat >= me->heartbeat_interval) {
+ /* send heartbeats */
+ if (jcr->file_bsock) {
+ bnet_sig(jcr->file_bsock, BNET_HEARTBEAT);
+ Dmsg0(400, "Send heartbeat to FD.\n");
+ }
+ if (jcr->dir_bsock) {
+ bnet_sig(jcr->dir_bsock, BNET_HEARTBEAT);
+ }
+ last_heartbeat = now;
+ }
+ }
+
+ /*
+ * Check if user unmounted the device while we were waiting
+ */
+ unmounted = (dev->dev_blocked == BST_UNMOUNTED) ||
+ (dev->dev_blocked == BST_UNMOUNTED_WAITING_FOR_SYSOP);
+
+ if (stat != ETIMEDOUT) { /* we blocked the device */
+ break; /* on error return */
+ }
+ if (dev->rem_wait_sec <= 0) { /* on exceeding wait time return */
+ Dmsg0(400, "Exceed wait time.\n");
+ break;
+ }
+
+ if (!unmounted && dev->vol_poll_interval &&
+ (now - first_start >= dev->vol_poll_interval)) {
+ Dmsg1(400, "In wait blocked=%s\n", edit_blocked_reason(dev));
+ dev->poll = true; /* returning a poll event */
+ break;
+ }
+ /*
+ * Check if user mounted the device while we were waiting
+ */
+ if (dev->dev_blocked == BST_MOUNT) { /* mount request ? */
+ stat = 0;
+ break;
+ }
+
+ add_wait = dev->wait_sec - (now - start);
+ if (add_wait < 0) {
+ add_wait = 0;
+ }
+ if (me->heartbeat_interval && add_wait > me->heartbeat_interval) {
+ add_wait = me->heartbeat_interval;
+ }
+ }
+
+ if (!unmounted) {
+ dev->dev_blocked = dev->dev_prev_blocked; /* restore entry state */
+ }
+ V(dev->mutex);
+ return stat;
+}
+
+
+/*
+ * Wait for Device to be released
+ *
+ */
+bool wait_for_device(DCR *dcr, const char *msg, bool first)
+{
+ struct timeval tv;
+ struct timezone tz;
+ struct timespec timeout;
+// time_t last_heartbeat = 0;
+ int stat = 0;
+ int add_wait;
+ DEVICE *dev = dcr->dev;
+ JCR *jcr = dcr->jcr;
+ bool ok = false;
+
+ P(device_release_mutex);
+
+ if (first) {
+ Jmsg(jcr, M_MOUNT, 0, msg);
+ }
+
+ /*
+ * Wait requested time (dev->rem_wait_sec). However, we also wake up every
+ * HB_TIME seconds and send a heartbeat to the FD and the Director
+ * to keep stateful firewalls from closing them down while waiting
+ * for the operator.
+ */
+ add_wait = jcr->rem_wait_sec;
+ if (me->heartbeat_interval && add_wait > me->heartbeat_interval) {
+ add_wait = me->heartbeat_interval;
+ }
+
+ for ( ; !job_canceled(jcr); ) {
+ time_t now, start;
+
+ gettimeofday(&tv, &tz);
+ timeout.tv_nsec = tv.tv_usec * 1000;
+ timeout.tv_sec = tv.tv_sec + add_wait;
+
+ Dmsg3(000, "I'm going to sleep on device %s. HB=%d wait=%d\n", dev->print_name(),
+ (int)me->heartbeat_interval, dev->wait_sec);
+ start = time(NULL);
+ /* Wait required time */
+ stat = pthread_cond_timedwait(&wait_device_release, &device_release_mutex, &timeout);
+ Dmsg1(000, "Wokeup from sleep on device stat=%d\n", stat);
+
+ now = time(NULL);
+ jcr->rem_wait_sec -= (now - start);
+
+#ifdef needed
+ /* Note, this always triggers the first time. We want that. */
+ if (me->heartbeat_interval) {
+ if (now - last_heartbeat >= me->heartbeat_interval) {
+ /* send heartbeats */
+ if (jcr->file_bsock) {
+ bnet_sig(jcr->file_bsock, BNET_HEARTBEAT);
+ Dmsg0(400, "Send heartbeat to FD.\n");
+ }
+ if (jcr->dir_bsock) {
+ bnet_sig(jcr->dir_bsock, BNET_HEARTBEAT);
+ }
+ last_heartbeat = now;
+ }
+ }
+#endif
+
+ if (stat != ETIMEDOUT) { /* if someone woke us up */
+ ok = true;
+ break; /* allow caller to examine device */
+ }
+ if (jcr->rem_wait_sec <= 0) { /* on exceeding wait time return */
+ Dmsg0(400, "Exceed wait time.\n");
+ if (!double_jcr_wait_time(jcr)) {
+ break; /* give up */
+ }
+ Jmsg(jcr, M_MOUNT, 0, msg);
+ }
+
+ add_wait = jcr->wait_sec - (now - start);
+ if (add_wait < 0) {
+ add_wait = 0;
+ }
+ if (me->heartbeat_interval && add_wait > me->heartbeat_interval) {
+ add_wait = me->heartbeat_interval;
+ }
+ }
+
+ V(device_release_mutex);
+ Dmsg1(000, "Return from wait_device ok=%d\n", ok);
+ return ok;
+}
+
+/*
+ * The jcr timers are used for waiting on any device
+ *
+ * Returns: true if time doubled
+ * false if max time expired
+ */
+static bool double_jcr_wait_time(JCR *jcr)
+{
+ jcr->wait_sec *= 2; /* double wait time */
+ if (jcr->wait_sec > jcr->max_wait) { /* but not longer than maxtime */
+ jcr->wait_sec = jcr->max_wait;
+ }
+ jcr->num_wait++;
+ jcr->rem_wait_sec = jcr->wait_sec;
+ if (jcr->num_wait >= jcr->max_num_wait) {
+ return false;
+ }
+ return true;
+}
/* */
#undef VERSION
#define VERSION "1.37.8"
-#define BDATE "25 March 2005"
-#define LSMDATE "25Mar05"
+#define BDATE "26 March 2005"
+#define LSMDATE "26Mar05"
/* Debug flags */
#undef DEBUG