+ Dmsg1(dbglvl, "return ok=%d find_next_vol\n", ok);
+
+ /* We keep the record of all previous volumes requested */
+ if (ok) {
+ add_volume_to_exclude_list(jcr, index, mr);;
+ }
+ return ok;
+}
+
+/*
+ * Check if any time limits or use limits have expired
+ * if so, set the VolStatus appropriately.
+ */
+bool has_volume_expired(JCR *jcr, MEDIA_DBR *mr)
+{
+ bool expired = false;
+ char ed1[50];
+ /*
+ * Check limits and expirations if "Append" and it has been used
+ * i.e. mr->VolJobs > 0
+ *
+ */
+ if (strcmp(mr->VolStatus, "Append") == 0 && mr->VolJobs > 0) {
+ /* First handle Max Volume Bytes */
+ if ((mr->MaxVolBytes > 0 && mr->VolBytes >= mr->MaxVolBytes)) {
+ Jmsg(jcr, M_INFO, 0, _("Max Volume bytes=%s exceeded. "
+ "Marking Volume \"%s\" as Full.\n"),
+ edit_uint64_with_commas(mr->MaxVolBytes, ed1), mr->VolumeName);
+ bstrncpy(mr->VolStatus, "Full", sizeof(mr->VolStatus));
+ expired = true;
+
+ /* Now see if Volume should only be used once */
+ } else if (mr->VolBytes > 0 && jcr->pool->use_volume_once) {
+ Jmsg(jcr, M_INFO, 0, _("Volume used once. "
+ "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
+ bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
+ expired = true;
+
+ /* Now see if Max Jobs written to volume */
+ } else if (mr->MaxVolJobs > 0 && mr->MaxVolJobs <= mr->VolJobs) {
+ Jmsg(jcr, M_INFO, 0, _("Max Volume jobs=%s exceeded. "
+ "Marking Volume \"%s\" as Used.\n"),
+ edit_uint64_with_commas(mr->MaxVolJobs, ed1), mr->VolumeName);
+ Dmsg3(dbglvl, "MaxVolJobs=%d JobId=%d Vol=%s\n", mr->MaxVolJobs,
+ (uint32_t)jcr->JobId, mr->VolumeName);
+ bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
+ expired = true;
+
+ /* Now see if Max Files written to volume */
+ } else if (mr->MaxVolFiles > 0 && mr->MaxVolFiles <= mr->VolFiles) {
+ Jmsg(jcr, M_INFO, 0, _("Max Volume files=%s exceeded. "
+ "Marking Volume \"%s\" as Used.\n"),
+ edit_uint64_with_commas(mr->MaxVolFiles, ed1), mr->VolumeName);
+ bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
+ expired = true;
+
+ /* Finally, check Use duration expiration */
+ } else if (mr->VolUseDuration > 0) {
+ utime_t now = time(NULL);
+ /* See if Vol Use has expired */
+ if (mr->VolUseDuration <= (now - mr->FirstWritten)) {
+ Jmsg(jcr, M_INFO, 0, _("Max configured use duration=%s sec. exceeded. "
+ "Marking Volume \"%s\" as Used.\n"),
+ edit_uint64_with_commas(mr->VolUseDuration, ed1), mr->VolumeName);
+ bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
+ expired = true;
+ }
+ }
+ }
+ if (expired) {
+ /* Need to update media */
+ Dmsg1(dbglvl, "Vol=%s has expired update media record\n", mr->VolumeName);
+ set_storageid_in_mr(NULL, mr);
+ if (!db_update_media_record(jcr, jcr->db, mr)) {
+ Jmsg(jcr, M_ERROR, 0, _("Catalog error updating volume \"%s\". ERR=%s"),
+ mr->VolumeName, db_strerror(jcr->db));
+ }
+ }
+ Dmsg2(dbglvl, "Vol=%s expired=%d\n", mr->VolumeName, expired);
+ return expired;
+}
+
+/*
+ * Try hard to recycle the current volume
+ *
+ * Returns: on failure - reason = NULL
+ * on success - reason - pointer to reason
+ */
+void check_if_volume_valid_or_recyclable(JCR *jcr, MEDIA_DBR *mr, const char **reason)
+{
+ int ok;
+
+ *reason = NULL;
+
+ /* Check if a duration or limit has expired */
+ if (has_volume_expired(jcr, mr)) {
+ *reason = _("volume has expired");
+ /* Keep going because we may be able to recycle volume */
+ }
+
+ /*
+ * Now see if we can use the volume as is
+ */
+ if (strcmp(mr->VolStatus, "Append") == 0 ||
+ strcmp(mr->VolStatus, "Recycle") == 0) {
+ *reason = NULL;
+ return;
+ }
+
+ /*
+ * Check if the Volume is already marked for recycling
+ */
+ if (strcmp(mr->VolStatus, "Purged") == 0) {
+ if (recycle_volume(jcr, mr)) {
+ Jmsg(jcr, M_INFO, 0, _("Recycled current volume \"%s\"\n"), mr->VolumeName);
+ *reason = NULL;
+ return;
+ } else {
+ /* In principle this shouldn't happen */
+ *reason = _("and recycling of current volume failed");
+ return;
+ }
+ }
+
+ /* At this point, the volume is not valid for writing */
+ *reason = _("but should be Append, Purged or Recycle");
+
+ /*
+ * What we're trying to do here is see if the current volume is
+ * "recyclable" - ie. if we prune all expired jobs off it, is
+ * it now possible to reuse it for the job that it is currently
+ * needed for?
+ */
+ if (!mr->Recycle) {
+ *reason = _("volume has recycling disabled");
+ return;
+ }
+ /*
+ * Check retention period from last written, but recycle to within
+ * a minute to try to catch close calls ...
+ */
+ if ((mr->LastWritten + mr->VolRetention - 60) < (utime_t)time(NULL)
+ && jcr->pool->recycle_current_volume
+ && (strcmp(mr->VolStatus, "Full") == 0 ||
+ strcmp(mr->VolStatus, "Used") == 0)) {
+ /*
+ * Attempt prune of current volume to see if we can
+ * recycle it for use.
+ */
+ UAContext *ua;
+
+ ua = new_ua_context(jcr);
+ ok = prune_volume(ua, mr);
+ free_ua_context(ua);
+
+ if (ok) {
+ /* If fully purged, recycle current volume */
+ if (recycle_volume(jcr, mr)) {
+ Jmsg(jcr, M_INFO, 0, _("Recycled current volume \"%s\"\n"), mr->VolumeName);
+ *reason = NULL;
+ } else {
+ *reason = _("but should be Append, Purged or Recycle (recycling of the "
+ "current volume failed)");
+ }
+ } else {
+ *reason = _("but should be Append, Purged or Recycle (cannot automatically "
+ "recycle current volume, as it still contains unpruned data "
+ "or the Volume Retention time has not expired.)");
+ }
+ }
+}
+
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+bool get_scratch_volume(JCR *jcr, bool InChanger, MEDIA_DBR *mr,
+ STORE *store)
+{
+ MEDIA_DBR smr; /* for searching scratch pool */
+ POOL_DBR spr, pr;
+ bool ok = false;
+ bool found = false;
+
+ /* Only one thread at a time can pull from the scratch pool */
+ P(mutex);
+ /*
+ * Get Pool record for Scratch Pool
+ * choose between ScratchPoolId and Scratch
+ * db_get_pool_numvols will first try ScratchPoolId,
+ * and then try the pool named Scratch
+ */
+ memset(&spr, 0, sizeof(spr));
+ bstrncpy(spr.Name, "Scratch", sizeof(spr.Name));
+ spr.PoolId = mr->ScratchPoolId;
+ if (db_get_pool_record(jcr, jcr->db, &spr)) {
+ smr.PoolId = spr.PoolId;
+ bstrncpy(smr.VolStatus, "Append", sizeof(smr.VolStatus)); /* want only appendable volumes */
+ bstrncpy(smr.MediaType, mr->MediaType, sizeof(smr.MediaType));
+
+ /*
+ * If we do not find a valid Scratch volume, try
+ * recycling any existing purged volumes, then
+ * try to take the oldest volume.
+ */
+ set_storageid_in_mr(store, &smr); /* put StorageId in new record */
+ if (db_find_next_volume(jcr, jcr->db, 1, InChanger, &smr)) {
+ found = true;
+
+ } else if (find_recycled_volume(jcr, InChanger, &smr, store)) {
+ found = true;
+
+ } else if (recycle_oldest_purged_volume(jcr, InChanger, &smr, store)) {
+ found = true;
+ }
+
+ if (found) {
+ POOL_MEM query(PM_MESSAGE);
+
+ /*
+ * Get pool record where the Scratch Volume will go to ensure
+ * that we can add a Volume.
+ */
+ memset(&pr, 0, sizeof(pr));
+ bstrncpy(pr.Name, jcr->pool->name(), sizeof(pr.Name));
+
+ if (!db_get_pool_numvols(jcr, jcr->db, &pr)) {
+ Jmsg(jcr, M_WARNING, 0, _("Unable to get Pool record: ERR=%s"),
+ db_strerror(jcr->db));
+ goto bail_out;
+ }
+
+ /* Make sure there is room for another volume */
+ if (pr.MaxVols > 0 && pr.NumVols >= pr.MaxVols) {
+ Jmsg(jcr, M_WARNING, 0, _("Unable add Scratch Volume, Pool \"%s\" full MaxVols=%d\n"),
+ jcr->pool->name(), pr.MaxVols);
+ goto bail_out;
+ }
+
+ mr->copy(&smr);
+ set_storageid_in_mr(store, mr);
+
+ /* Set default parameters from current pool */
+ set_pool_dbr_defaults_in_media_dbr(mr, &pr);
+
+ /*
+ * set_pool_dbr_defaults_in_media_dbr set VolStatus to Append,
+ * we could have Recycled media, also, we retain the old
+ * RecyclePoolId.
+ */
+ bstrncpy(mr->VolStatus, smr.VolStatus, sizeof(smr.VolStatus));
+ mr->RecyclePoolId = smr.RecyclePoolId;
+
+ if (!db_update_media_record(jcr, jcr->db, mr)) {
+ Jmsg(jcr, M_WARNING, 0, _("Failed to move Scratch Volume. ERR=%s\n"),
+ db_strerror(jcr->db));
+ goto bail_out;
+ }
+
+ Jmsg(jcr, M_INFO, 0, _("Using Volume \"%s\" from '%s' %spool.\n"),
+ mr->VolumeName, spr.Name,
+ ((strcmp(spr.Name, "Scratch") == 0) ? "" : "Scratch "));
+
+ ok = true;
+ }
+ }
+bail_out:
+ V(mutex);