/* * This routine is meant to be called once the first pass * to ensure that we have a candidate volume to mount. * Otherwise, we ask the sysop to created one. */ bool DCR::find_a_volume() { DCR *dcr = this; if (!is_suitable_volume_mounted()) { bool have_vol = false; /* Do we have a candidate volume? */ if (dev->vol) { bstrncpy(VolumeName, dev->vol->vol_name, sizeof(VolumeName)); have_vol = dir_get_volume_info(this, GET_VOL_INFO_FOR_WRITE); } /* * Get Director's idea of what tape we should have mounted. * in dcr->VolCatInfo */ if (!have_vol) { Dmsg0(200, "Before dir_find_next_appendable_volume.\n"); while (!dir_find_next_appendable_volume(dcr)) { Dmsg0(200, "not dir_find_next\n"); if (job_canceled(jcr)) { unlock_volumes(); return false; } unlock_volumes(); if (!dir_ask_sysop_to_create_appendable_volume(dcr)) { return false; } lock_volumes(); Dmsg0(150, "Again dir_find_next_append...\n"); } } } return true; }
static void debug_list_volumes(const char *imsg) { VOLRES *vol; POOL_MEM msg(PM_MESSAGE); lock_volumes(); foreach_dlist(vol, vol_list) { 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()); } else { 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()); } unlock_volumes(); }
/** * Get info on the next appendable volume in the Director's database * * Returns: true on success dcr->VolumeName is volume * reserve_volume() called on Volume name * false on failure dcr->VolumeName[0] == 0 * also sets dcr->found_in_use if at least one * in use volume was found. * * Volume information returned in dcr * */ bool dir_find_next_appendable_volume(DCR *dcr) { JCR *jcr = dcr->jcr; BSOCK *dir = jcr->dir_bsock; bool rtn; char lastVolume[MAX_NAME_LENGTH]; Dmsg2(dbglvl, "dir_find_next_appendable_volume: reserved=%d Vol=%s\n", dcr->is_reserved(), dcr->VolumeName); /* * Try the twenty oldest or most available volumes. Note, * the most available could already be mounted on another * drive, so we continue looking for a not in use Volume. */ lock_volumes(); P(vol_info_mutex); dcr->clear_found_in_use(); lastVolume[0] = 0; for (int vol_index=1; vol_index < 20; vol_index++) { bash_spaces(dcr->media_type); bash_spaces(dcr->pool_name); dir->fsend(Find_media, jcr->Job, vol_index, dcr->pool_name, dcr->media_type); unbash_spaces(dcr->media_type); unbash_spaces(dcr->pool_name); Dmsg1(dbglvl, ">dird %s", dir->msg); if (do_get_volume_info(dcr)) { /* Give up if we get the same volume name twice */ if (lastVolume[0] && bstrcmp(lastVolume, dcr->VolumeName)) { Dmsg1(dbglvl, "Got same vol = %s\n", lastVolume); break; } bstrncpy(lastVolume, dcr->VolumeName, sizeof(lastVolume)); if (dcr->can_i_write_volume()) { Dmsg1(dbglvl, "Call reserve_volume for write. Vol=%s\n", dcr->VolumeName); if (reserve_volume(dcr, dcr->VolumeName) == NULL) { Dmsg2(dbglvl, "Could not reserve volume %s on %s\n", dcr->VolumeName, dcr->dev->print_name()); continue; } Dmsg1(dbglvl, "dir_find_next_appendable_volume return true. vol=%s\n", dcr->VolumeName); rtn = true; goto get_out; } else { Dmsg1(dbglvl, "Volume %s is in use.\n", dcr->VolumeName); /* If volume is not usable, it is in use by someone else */ dcr->set_found_in_use(); continue; } } Dmsg2(dbglvl, "No vol. index %d return false. dev=%s\n", vol_index, dcr->dev->print_name()); break; } rtn = false; dcr->VolumeName[0] = 0; get_out: V(vol_info_mutex); unlock_volumes(); return rtn; }
/* * This job is done, so release the device. From a Unix standpoint, * the device remains open. * * 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) { utime_t now; JCR *jcr = dcr->jcr; DEVICE *dev = dcr->dev; bool ok = true; char tbuf[100]; int was_blocked = BST_NOT_BLOCKED; /* * Capture job statistics now that we are done using this device. */ now = (utime_t)time(NULL); update_job_statistics(jcr, now); dev->Lock(); 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 */ dcr->clear_reserved(); if (dev->can_read()) { VOLUME_CAT_INFO *vol = &dev->VolCatInfo; dev->clear_read(); /* clear read bit */ Dmsg2(150, "dir_update_vol_info. label=%d Vol=%s\n", dev->is_labeled(), vol->VolCatName); if (dev->is_labeled() && vol->VolCatName[0] != 0) { dcr->dir_update_volume_info(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 and may not be positioned correctly, * so the job_media_record and update_vol_info have already been done, * which means we skip them here. */ dev->num_writers--; Dmsg1(100, "There are %d writers in release_device\n", dev->num_writers); if (dev->is_labeled()) { Dmsg2(200, "dir_create_jobmedia. Release vol=%s dev=%s\n", dev->getVolCatName(), dev->print_name()); if (!dev->at_weot() && !dcr->dir_create_jobmedia_record(false)) { Jmsg2(jcr, M_FATAL, 0, _("Could not create JobMedia record for Volume=\"%s\" Job=%s\n"), dcr->getVolCatName(), jcr->Job); } /* * 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); } if (!dev->at_weot()) { dev->VolCatInfo.VolCatFiles = dev->file; /* set number of files */ /* * Note! do volume update before close, which zaps VolCatInfo */ dcr->dir_update_volume_info(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 */ } } } else { /* * If we reach here, it is most likely because the job 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))) { dev->close(dcr); free_volume(dev); } unlock_volumes(); /* * Fire off Alert command and include any output */ if (!job_canceled(jcr)) { if (!dcr->device->drive_tapealert_enabled && dcr->device->alert_command) { int status = 1; POOLMEM *alert, *line; BPIPE *bpipe; alert = get_pool_memory(PM_FNAME); line = get_pool_memory(PM_FNAME); alert = edit_device_codes(dcr, alert, dcr->device->alert_command, ""); /* * Wait maximum 5 minutes */ bpipe = open_bpipe(alert, 60 * 5, "r"); if (bpipe) { while (bfgets(line, bpipe->rfd)) { Jmsg(jcr, M_ALERT, 0, _("Alert: %s"), line); } status = close_bpipe(bpipe); } else { status = errno; } if (status != 0) { berrno be; Jmsg(jcr, M_ALERT, 0, _("3997 Bad alert command: %s: ERR=%s.\n"), alert, be.bstrerror(status)); } Dmsg1(400, "alert status=%d\n", status); free_pool_memory(alert); free_pool_memory(line); } else { /* * If all reservations are cleared for this device raise an event that SD plugins can register to. */ if (dev->num_reserved() == 0) { generate_plugin_event(jcr, bsdEventDeviceReleased, dcr); } } } pthread_cond_broadcast(&dev->wait_next_vol); Dmsg2(100, "JobId=%u broadcast wait_device_release at %s\n", (uint32_t)jcr->JobId, bstrftimes(tbuf, sizeof(tbuf), (utime_t)time(NULL))); release_device_cond(); /* * 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->Unlock(); } if (dcr->keep_dcr) { detach_dcr_from_dev(dcr); } else { free_dcr(dcr); } Dmsg2(100, "Device %s released by JobId=%u\n", dev->print_name(), (uint32_t)jcr->JobId); return ok; }
/* * If release is set, we rewind the current volume, * which we no longer want, and ask the user (console) * to mount the next volume. * * Continue trying until we get it, and then ensure * that we can write on it. * * This routine returns a 0 only if it is REALLY * impossible to get the requested Volume. * */ bool DCR::mount_next_write_volume() { int retry = 0; bool ask = false, recycle, autochanger; bool do_find = true; int mode; DCR *dcr = this; Dmsg2(150, "Enter mount_next_volume(release=%d) dev=%s\n", dev->must_unload(), dev->print_name()); init_device_wait_timers(dcr); lock_volumes(); /* * Attempt to mount the next volume. If something non-fatal goes * wrong, we come back here to re-try (new op messages, re-read * Volume, ...) */ mount_next_vol: Dmsg1(150, "mount_next_vol retry=%d\n", retry); /* Ignore retry if this is poll request */ if (!dev->poll && retry++ > 4) { /* Last ditch effort before giving up, force operator to respond */ VolCatInfo.Slot = 0; unlock_volumes(); if (!dir_ask_sysop_to_mount_volume(dcr, ST_APPEND)) { Jmsg(jcr, M_FATAL, 0, _("Too many errors trying to mount device %s.\n"), dev->print_name()); goto no_lock_bail_out; } lock_volumes(); Dmsg1(150, "Continue after dir_ask_sysop_to_mount. must_load=%d\n", dev->must_load()); } if (job_canceled(jcr)) { Jmsg(jcr, M_FATAL, 0, _("Job %d canceled.\n"), jcr->JobId); goto bail_out; } recycle = false; if (retry >= 2) { do_find = false; } if (dev->must_unload()) { ask = true; /* ask operator to mount tape */ do_find = true; /* re-find a volume after unload */ } do_unload(); do_swapping(true /*is_writing*/); do_load(true /*is_writing*/); if (do_find && !find_a_volume()) { goto no_lock_bail_out; } if (job_canceled(jcr)) { goto bail_out; } Dmsg3(150, "After find_next_append. Vol=%s Slot=%d Parts=%d\n", VolCatInfo.VolCatName, VolCatInfo.Slot, VolCatInfo.VolCatParts); /* * Get next volume and ready it for append * This code ensures that the device is ready for * writing. We start from the assumption that there * may not be a tape mounted. * * If the device is a file, we create the output * file. If it is a tape, we check the volume name * and move the tape to the end of data. * */ if (autoload_device(dcr, true/*writing*/, NULL) > 0) { autochanger = true; ask = false; } else { autochanger = false; VolCatInfo.Slot = 0; ask = retry >= 2; } Dmsg1(150, "autoload_dev returns %d\n", autochanger); /* * If we autochanged to correct Volume or (we have not just * released the Volume AND we can automount) we go ahead * and read the label. If there is no tape in the drive, * we will fail, recurse and ask the operator the next time. */ if (!dev->must_unload() && dev->is_tape() && dev->has_cap(CAP_AUTOMOUNT)) { Dmsg0(250, "(1)Ask=0\n"); ask = false; /* don't ask SYSOP this time */ } /* Don't ask if not removable */ if (!dev->is_removable()) { Dmsg0(250, "(2)Ask=0\n"); ask = false; } Dmsg2(250, "Ask=%d autochanger=%d\n", ask, autochanger); if (ask) { unlock_volumes(); if (!dir_ask_sysop_to_mount_volume(dcr, ST_APPEND)) { Dmsg0(150, "Error return ask_sysop ...\n"); goto no_lock_bail_out; } lock_volumes(); } if (job_canceled(jcr)) { goto bail_out; } Dmsg3(150, "want vol=%s devvol=%s dev=%s\n", VolumeName, dev->VolHdr.VolumeName, dev->print_name()); if (dev->poll && dev->has_cap(CAP_CLOSEONPOLL)) { dev->close(); } /* Ensure the device is open */ if (dev->has_cap(CAP_STREAM)) { mode = OPEN_WRITE_ONLY; } else { mode = OPEN_READ_WRITE; } /* Try autolabel if enabled */ if (dev->open(dcr, mode) < 0) { try_autolabel(false); /* try to create a new volume label */ } while (dev->open(dcr, mode) < 0) { Dmsg1(150, "open_device failed: ERR=%s\n", dev->bstrerror()); if ((dev->is_file() && dev->is_removable()) || dev->is_dvd()) { bool ok = true; Dmsg0(150, "call scan_dir_for_vol\n"); if (dev->is_dvd()) { if (!dev->mount(0)) { ok = false; } } if (ok && dev->scan_dir_for_volume(dcr)) { if (dev->open(dcr, mode) >= 0) { break; /* got a valid volume */ } } if (ok && dev->is_dvd()) { dev->unmount(0); } } if (try_autolabel(false) == try_read_vol) { break; /* created a new volume label */ } Dmsg0(50, "set_unload\n"); dev->set_unload(); /* force ask sysop */ ask = true; Dmsg0(150, "goto mount_next_vol\n"); goto mount_next_vol; } /* * Now check the volume label to make sure we have the right tape mounted */ read_volume: switch (check_volume_label(ask, autochanger)) { case check_next_vol: Dmsg0(50, "set_unload\n"); dev->set_unload(); /* want a different Volume */ Dmsg0(150, "goto mount_next_vol\n"); goto mount_next_vol; case check_read_vol: goto read_volume; case check_error: goto bail_out; case check_ok: break; } /* * See if we have a fresh tape or a tape with data. * * Note, if the LabelType is PRE_LABEL, it was labeled * but never written. If so, rewrite the label but set as * VOL_LABEL. We rewind and return the label (reconstructed) * in the block so that in the case of a new tape, data can * be appended just after the block label. If we are writing * a second volume, the calling routine will write the label * before writing the overflow block. * * If the tape is marked as Recycle, we rewrite the label. */ recycle = strcmp(dev->VolCatInfo.VolCatStatus, "Recycle") == 0; if (dev->VolHdr.LabelType == PRE_LABEL || recycle) { if (!rewrite_volume_label(dcr, recycle)) { mark_volume_in_error(); goto mount_next_vol; } } else { /* * OK, at this point, we have a valid Bacula label, but * we need to position to the end of the volume, since we are * just now putting it into append mode. */ Dmsg0(200, "Device previously written, moving to end of data\n"); Jmsg(jcr, M_INFO, 0, _("Volume \"%s\" previously written, moving to end of data.\n"), VolumeName); if (!dev->eod(dcr)) { Jmsg(jcr, M_ERROR, 0, _("Unable to position to end of data on device %s: ERR=%s\n"), dev->print_name(), dev->bstrerror()); mark_volume_in_error(); goto mount_next_vol; } if (!is_eod_valid()) { Dmsg0(150, "goto mount_next_vol\n"); goto mount_next_vol; } dev->VolCatInfo.VolCatMounts++; /* Update mounts */ Dmsg1(150, "update volinfo mounts=%d\n", dev->VolCatInfo.VolCatMounts); if (!dir_update_volume_info(dcr, false, false)) { goto bail_out; } /* Return an empty block */ empty_block(block); /* we used it for reading so set for write */ } dev->set_append(); Dmsg1(150, "set APPEND, normal return from mount_next_write_volume. dev=%s\n", dev->print_name()); unlock_volumes(); return true; bail_out: unlock_volumes(); no_lock_bail_out: return false; }
/** * Get info on the next appendable volume in the Director's database * * Returns: true on success dcr->VolumeName is volume * reserve_volume() called on Volume name * false on failure dcr->VolumeName[0] == 0 * also sets dcr->found_in_use if at least one * in use volume was found. * * Volume information returned in dcr */ bool SD_DCR::dir_find_next_appendable_volume() { bool retval; BSOCK *dir = jcr->dir_bsock; POOL_MEM unwanted_volumes(PM_MESSAGE); Dmsg2(dbglvl, "dir_find_next_appendable_volume: reserved=%d Vol=%s\n", is_reserved(), VolumeName); /* * Try the twenty oldest or most available volumes. Note, * the most available could already be mounted on another * drive, so we continue looking for a not in use Volume. */ lock_volumes(); P(vol_info_mutex); clear_found_in_use(); pm_strcpy(unwanted_volumes, ""); for (int vol_index = 1; vol_index < 20; vol_index++) { bash_spaces(media_type); bash_spaces(pool_name); bash_spaces(unwanted_volumes.c_str()); dir->fsend(Find_media, jcr->Job, vol_index, pool_name, media_type, unwanted_volumes.c_str()); unbash_spaces(media_type); unbash_spaces(pool_name); unbash_spaces(unwanted_volumes.c_str()); Dmsg1(dbglvl, ">dird %s", dir->msg); if (do_get_volume_info(this)) { if (vol_index == 1) { pm_strcpy(unwanted_volumes, VolumeName); } else { pm_strcat(unwanted_volumes, ","); pm_strcat(unwanted_volumes, VolumeName); } if (can_i_write_volume()) { Dmsg1(dbglvl, "Call reserve_volume for write. Vol=%s\n", VolumeName); if (reserve_volume(this, VolumeName) == NULL) { Dmsg2(dbglvl, "Could not reserve volume %s on %s\n", VolumeName, dev->print_name()); continue; } Dmsg1(dbglvl, "dir_find_next_appendable_volume return true. vol=%s\n", VolumeName); retval = true; goto get_out; } else { Dmsg1(dbglvl, "Volume %s is in use.\n", VolumeName); /* * If volume is not usable, it is in use by someone else */ set_found_in_use(); continue; } } Dmsg2(dbglvl, "No vol. index %d return false. dev=%s\n", vol_index, dev->print_name()); break; } retval = false; VolumeName[0] = 0; get_out: V(vol_info_mutex); unlock_volumes(); return retval; }