3 * Bacula Director -- next_vol -- handles finding the next
4 * volume for append. Split out of catreq.c August MMIII
5 * catalog request from the Storage daemon.
7 * Kern Sibbald, March MMI
12 Copyright (C) 2001-2005 Kern Sibbald
14 This program is free software; you can redistribute it and/or
15 modify it under the terms of the GNU General Public License
16 version 2 as amended with additional clauses defined in the
17 file LICENSE in the main source directory.
19 This program is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 the file LICENSE for additional details.
31 * mr.PoolId must be set
35 * MEDIA_DBR mr (zeroed out)
36 * create -- whether or not to create a new volume
38 int find_next_volume_for_append(JCR *jcr, MEDIA_DBR *mr, bool create)
43 STORE *store = jcr->store;
45 bstrncpy(mr->MediaType, store->media_type, sizeof(mr->MediaType));
46 Dmsg2(100, "CatReq FindMedia: Id=%d, MediaType=%s\n", (int)mr->PoolId, mr->MediaType);
48 * If we are using an Autochanger, restrict Volume
49 * search to the Autochanger on the first pass
51 InChanger = store->autochanger;
53 * Find the Next Volume for Append
57 bstrncpy(mr->VolStatus, "Append", sizeof(mr->VolStatus)); /* want only appendable volumes */
59 * 1. Look for volume with "Append" status.
61 ok = db_find_next_volume(jcr, jcr->db, 1, InChanger, mr);
62 Dmsg2(100, "catreq after find_next_vol ok=%d FW=%d\n", ok, mr->FirstWritten);
65 * 2. Try finding a recycled volume
67 ok = find_recycled_volume(jcr, InChanger, mr);
68 Dmsg2(100, "find_recycled_volume %d FW=%d\n", ok, mr->FirstWritten);
71 * 3. Try recycling any purged volume
73 ok = recycle_oldest_purged_volume(jcr, InChanger, mr);
76 * 4. Try pruning Volumes
79 ok = recycle_oldest_purged_volume(jcr, InChanger, mr);
83 continue; /* retry again accepting any volume */
86 Dmsg2(200, "find_recycled_volume2 %d FW=%d\n", ok, mr->FirstWritten);
89 * 5. Try "creating" a new Volume
91 ok = newVolume(jcr, mr);
100 char ed1[50], ed2[50];
102 * 6. Try pulling a volume from the Scratch pool
104 memset(&pr, 0, sizeof(pr));
105 bstrncpy(pr.Name, "Scratch", sizeof(pr.Name));
106 if (db_get_pool_record(jcr, jcr->db, &pr)) {
107 memset(&smr, 0, sizeof(smr));
108 smr.PoolId = pr.PoolId;
109 bstrncpy(smr.VolStatus, "Append", sizeof(smr.VolStatus)); /* want only appendable volumes */
110 bstrncpy(smr.MediaType, mr->MediaType, sizeof(smr.MediaType));
111 if (db_find_next_volume(jcr, jcr->db, 1, InChanger, &smr)) {
112 query = get_pool_memory(PM_MESSAGE);
114 Mmsg(query, "UPDATE Media SET PoolId=%s WHERE MediaId=%s",
115 edit_int64(mr->PoolId, ed1),
116 edit_int64(smr.MediaId, ed2));
117 ok = db_sql_query(jcr->db, query, NULL, NULL);
119 Jmsg(jcr, M_INFO, 0, _("Using Volume \"%s\" from 'Scratch' pool.\n"),
121 /* Set new Pool Id in smr record, then copy it to mr */
122 smr.PoolId = mr->PoolId;
123 memcpy(mr, &smr, sizeof(MEDIA_DBR));
128 * Look at more drastic ways to find an Appendable Volume
130 if (!ok && (jcr->pool->purge_oldest_volume ||
131 jcr->pool->recycle_oldest_volume)) {
132 Dmsg2(200, "No next volume found. PurgeOldest=%d\n RecyleOldest=%d",
133 jcr->pool->purge_oldest_volume, jcr->pool->recycle_oldest_volume);
134 /* Find oldest volume to recycle */
135 ok = db_find_next_volume(jcr, jcr->db, -1, InChanger, mr);
136 Dmsg1(400, "Find oldest=%d\n", ok);
139 Dmsg0(400, "Try purge.\n");
141 * 5. Try to purging oldest volume only if not UA calling us.
143 ua = new_ua_context(jcr);
144 if (jcr->pool->purge_oldest_volume && create) {
145 Jmsg(jcr, M_INFO, 0, _("Purging oldest volume \"%s\"\n"), mr->VolumeName);
146 ok = purge_jobs_from_volume(ua, mr);
148 * 5. or try recycling the oldest volume
150 } else if (jcr->pool->recycle_oldest_volume) {
151 Jmsg(jcr, M_INFO, 0, _("Pruning oldest volume \"%s\"\n"), mr->VolumeName);
152 ok = prune_volume(ua, mr);
156 ok = recycle_volume(jcr, mr);
157 Dmsg1(400, "Recycle after purge oldest=%d\n", ok);
162 Dmsg2(100, "VolJobs=%d FirstWritten=%d\n", mr->VolJobs, mr->FirstWritten);
164 /* If we can use the volume, check if it is expired */
165 if (has_volume_expired(jcr, mr)) {
166 if (retry++ < 200) { /* sanity check */
167 continue; /* try again from the top */
169 Jmsg(jcr, M_ERROR, 0, _(
170 "We seem to be looping trying to find the next volume. I give up.\n"));
181 * Check if any time limits or use limits have expired
182 * if so, set the VolStatus appropriately.
184 bool has_volume_expired(JCR *jcr, MEDIA_DBR *mr)
186 bool expired = false;
188 * Check limits and expirations if "Append" and it has been used
189 * i.e. mr->VolJobs > 0
192 if (strcmp(mr->VolStatus, "Append") == 0 && mr->VolJobs > 0) {
193 /* First handle Max Volume Bytes */
194 if ((mr->MaxVolBytes > 0 && mr->VolBytes >= mr->MaxVolBytes)) {
195 Jmsg(jcr, M_INFO, 0, _("Max Volume bytes exceeded. "
196 "Marking Volume \"%s\" as Full.\n"), mr->VolumeName);
197 bstrncpy(mr->VolStatus, "Full", sizeof(mr->VolStatus));
200 /* Now see if Volume should only be used once */
201 } else if (mr->VolBytes > 0 && jcr->pool->use_volume_once) {
202 Jmsg(jcr, M_INFO, 0, _("Volume used once. "
203 "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
204 bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
207 /* Now see if Max Jobs written to volume */
208 } else if (mr->MaxVolJobs > 0 && mr->MaxVolJobs <= mr->VolJobs) {
209 Jmsg(jcr, M_INFO, 0, _("Max Volume jobs exceeded. "
210 "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
211 bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
214 /* Now see if Max Files written to volume */
215 } else if (mr->MaxVolFiles > 0 && mr->MaxVolFiles <= mr->VolFiles) {
216 Jmsg(jcr, M_INFO, 0, _("Max Volume files exceeded. "
217 "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
218 bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
221 /* Finally, check Use duration expiration */
222 } else if (mr->VolUseDuration > 0) {
223 utime_t now = time(NULL);
224 /* See if Vol Use has expired */
225 if (mr->VolUseDuration <= (now - mr->FirstWritten)) {
226 Jmsg(jcr, M_INFO, 0, _("Max configured use duration exceeded. "
227 "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
228 bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
234 /* Need to update media */
235 if (!db_update_media_record(jcr, jcr->db, mr)) {
236 Jmsg(jcr, M_ERROR, 0, _("Catalog error updating volume \"%s\". ERR=%s"),
237 mr->VolumeName, db_strerror(jcr->db));
244 * Try hard to recycle the current volume
246 * Returns: on failure - reason = NULL
247 * on success - reason - pointer to reason
249 void check_if_volume_valid_or_recyclable(JCR *jcr, MEDIA_DBR *mr, const char **reason)
255 /* Check if a duration or limit has expired */
256 if (has_volume_expired(jcr, mr)) {
257 *reason = _("volume has expired");
258 /* Keep going because we may be able to recycle volume */
262 * Now see if we can use the volume as is
264 if (strcmp(mr->VolStatus, "Append") == 0 ||
265 strcmp(mr->VolStatus, "Recycle") == 0) {
271 * Check if the Volume is already marked for recycling
273 if (strcmp(mr->VolStatus, "Purged") == 0) {
274 if (recycle_volume(jcr, mr)) {
275 Jmsg(jcr, M_INFO, 0, _("Recycled current volume \"%s\"\n"), mr->VolumeName);
279 /* In principle this shouldn't happen */
280 *reason = _("and recycling of current volume failed");
285 /* At this point, the volume is not valid for writing */
286 *reason = _("but should be Append, Purged or Recycle");
289 * What we're trying to do here is see if the current volume is
290 * "recyclable" - ie. if we prune all expired jobs off it, is
291 * it now possible to reuse it for the job that it is currently
294 if ((mr->LastWritten + mr->VolRetention) < (utime_t)time(NULL)
295 && mr->Recycle && jcr->pool->recycle_current_volume
296 && (strcmp(mr->VolStatus, "Full") == 0 ||
297 strcmp(mr->VolStatus, "Used") == 0)) {
299 * Attempt prune of current volume to see if we can
300 * recycle it for use.
304 ua = new_ua_context(jcr);
305 ok = prune_volume(ua, mr);
309 /* If fully purged, recycle current volume */
310 if (recycle_volume(jcr, mr)) {
311 Jmsg(jcr, M_INFO, 0, _("Recycled current volume \"%s\"\n"), mr->VolumeName);
314 *reason = _("but should be Append, Purged or Recycle (recycling of the "
315 "current volume failed)");
318 *reason = _("but should be Append, Purged or Recycle (cannot automatically "
319 "recycle current volume, as it still contains unpruned data)");