1 /*
2 Bacula(R) - The Network Backup Solution
3
4 Copyright (C) 2000-2020 Kern Sibbald
5
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
8
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
13
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
16
17 Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20 *
21 * Bacula Director -- next_vol -- handles finding the next
22 * volume for append. Split out of catreq.c August MMIII
23 * catalog request from the Storage daemon.
24
25 * Kern Sibbald, March MMI
26 *
27 */
28
29 #include "bacula.h"
30 #include "dird.h"
31
32 static int const dbglvl = 50; /* debug level */
33 /*
34 * We setup the StorageId or StorageId group if it is
35 * an autochanger from the Storage and put it in
36 * the media record.
37 * store == NULL => use existing StorageId
38 */
set_storageid_in_mr(STORE * store,MEDIA_DBR * mr)39 void set_storageid_in_mr(STORE *store, MEDIA_DBR *mr)
40 {
41 if (store == NULL) {
42 /* Just use the plain (single) StorageId */
43 mr->sid_group = edit_int64(mr->StorageId, mr->sid);
44 return;
45 }
46
47 /* At this point we know store != NULL */
48 mr->StorageId = store->StorageId;
49 /* Get to the parent of the autochanger (if any) */
50 if (store->changer) {
51 store = store->changer;
52 mr->StorageId = store->StorageId;
53 }
54 /* Go to the master shared storage head (if any) */
55 if (store->shared_storage && store->shared_storage->ac_group) {
56 store = store->shared_storage;
57 }
58 /* If it is an autochanger we should have an ac_group */
59 if (store->autochanger && store->ac_group) {
60 /* Note we keep the StorageId of the local autochanger */
61 mr->sid_group = store->ac_group;
62 } else {
63 /* Otherwise, we just use the plain (single) StorageId */
64 mr->sid_group = edit_int64(mr->StorageId, mr->sid);
65 }
66 }
67
add_volume_to_exclude_list(JCR * jcr,int index,MEDIA_DBR * mr)68 static void add_volume_to_exclude_list(JCR *jcr, int index, MEDIA_DBR *mr)
69 {
70 char ed1[50];
71 if (index == 1) {
72 *jcr->next_vol_list = 0;
73
74 } else if (*jcr->next_vol_list) {
75 pm_strcat(jcr->next_vol_list, ",");
76 }
77 pm_strcat(jcr->next_vol_list, edit_int64(mr->MediaId, ed1));
78
79 /* The list is valid only in find_next_volume_for_append() */
80 mr->exclude_list = NULL;
81 }
82
set_volume_to_exclude_list(JCR * jcr,int index,MEDIA_DBR * mr)83 static void set_volume_to_exclude_list(JCR *jcr, int index, MEDIA_DBR *mr)
84 {
85 if (index == 1) {
86 *jcr->next_vol_list = 0;
87 }
88 mr->exclude_list = jcr->next_vol_list;
89 }
90
91 /*
92 * Items needed:
93 * mr.PoolId must be set
94 * mr.ScratchPoolId could be set (used if create==true)
95 * jcr->wstore
96 * jcr->db
97 * jcr->pool
98 * MEDIA_DBR mr with PoolId set
99 * create -- whether or not to create a new volume
100 */
find_next_volume_for_append(JCR * jcr,MEDIA_DBR * mr,int index,bool create,bool prune,POOL_MEM & errmsg)101 int find_next_volume_for_append(JCR *jcr, MEDIA_DBR *mr, int index,
102 bool create, bool prune, POOL_MEM &errmsg)
103 {
104 int retry = 0;
105 bool ok;
106 bool InChanger;
107 STORE *store = jcr->wstore;
108
109 bstrncpy(mr->MediaType, store->media_type, sizeof(mr->MediaType));
110 Dmsg6(dbglvl, "find_next_vol_for_append: JobId=%u PoolId=%d, MediaType=%s index=%d create=%d prune=%d\n",
111 (uint32_t)jcr->JobId, (int)mr->PoolId, mr->MediaType, index,
112 create, prune);
113 /*
114 * If we are using an Autochanger, restrict Volume
115 * search to the Autochanger on the first pass
116 */
117 InChanger = (store->autochanger)? true : false;
118
119 /* Make sure we don't send two times the same volume in the same session */
120 set_volume_to_exclude_list(jcr, index, mr);
121
122 /*
123 * Find the Next Volume for Append
124 */
125 db_lock(jcr->db);
126 for ( ;; ) {
127 pm_strcpy(errmsg, "");
128 bstrncpy(mr->VolStatus, "Append", sizeof(mr->VolStatus)); /* want only appendable volumes */
129 /*
130 * 1. Look for volume with "Append" status.
131 */
132 set_storageid_in_mr(store, mr); /* put StorageId in new record */
133 ok = db_find_next_volume(jcr, jcr->db, index, InChanger, mr);
134
135 if (!ok) {
136 /*
137 * No volume found, apply algorithm
138 */
139 Dmsg4(dbglvl, "after find_next_vol ok=%d index=%d InChanger=%d Vstat=%s\n",
140 ok, index, InChanger, mr->VolStatus);
141 /*
142 * 2. Try finding a recycled volume
143 */
144 ok = find_recycled_volume(jcr, InChanger, mr, store);
145 set_storageid_in_mr(store, mr); /* put StorageId in new record */
146 Dmsg2(dbglvl, "find_recycled_volume ok=%d FW=%d\n", ok, mr->FirstWritten);
147 if (!ok) {
148 /*
149 * 3. Try recycling any purged volume
150 */
151 ok = recycle_oldest_purged_volume(jcr, InChanger, mr, store);
152 set_storageid_in_mr(store, mr); /* put StorageId in new record */
153 if (!ok) {
154 /*
155 * 4. Try pruning Volumes
156 */
157 if (prune) {
158 Dmsg0(dbglvl, "Call prune_volumes\n");
159 prune_volumes(jcr, InChanger, mr, store);
160 }
161 ok = recycle_oldest_purged_volume(jcr, InChanger, mr, store);
162 set_storageid_in_mr(store, mr); /* put StorageId in new record */
163 if (!ok && create) {
164 Dmsg4(dbglvl, "after prune volumes_vol ok=%d index=%d InChanger=%d Vstat=%s\n",
165 ok, index, InChanger, mr->VolStatus);
166 /*
167 * 5. Try pulling a volume from the Scratch pool
168 */
169 ok = get_scratch_volume(jcr, InChanger, mr, store);
170 set_storageid_in_mr(store, mr); /* put StorageId in new record */
171 Dmsg4(dbglvl, "after get scratch volume ok=%d index=%d InChanger=%d Vstat=%s\n",
172 ok, index, InChanger, mr->VolStatus);
173 }
174 /*
175 * If we are using an Autochanger and have not found
176 * a volume, retry looking for any volume.
177 */
178 if (!ok && InChanger) {
179 InChanger = false;
180 continue; /* retry again accepting any volume */
181 }
182 }
183 }
184
185
186 if (!ok && create) {
187 /*
188 * 6. Try "creating" a new Volume
189 */
190 ok = newVolume(jcr, mr, store, errmsg);
191 }
192 /*
193 * Look at more drastic ways to find an Appendable Volume
194 */
195 if (!ok && (jcr->pool->purge_oldest_volume ||
196 jcr->pool->recycle_oldest_volume)) {
197 Dmsg2(dbglvl, "No next volume found. PurgeOldest=%d\n RecyleOldest=%d",
198 jcr->pool->purge_oldest_volume, jcr->pool->recycle_oldest_volume);
199 /* Find oldest volume to recycle */
200 set_storageid_in_mr(store, mr); /* update storage id */
201 ok = db_find_next_volume(jcr, jcr->db, -1, InChanger, mr);
202 set_storageid_in_mr(store, mr); /* update storageid */
203 Dmsg1(dbglvl, "Find oldest=%d Volume\n", ok);
204 if (ok && prune) {
205 UAContext *ua;
206 Dmsg0(dbglvl, "Try purge Volume.\n");
207 /*
208 * 7. Try to purging oldest volume only if not UA calling us.
209 */
210 ua = new_ua_context(jcr);
211 if (jcr->pool->purge_oldest_volume && create) {
212 Jmsg(jcr, M_INFO, 0, _("Purging oldest volume \"%s\"\n"), mr->VolumeName);
213 ok = purge_jobs_from_volume(ua, mr);
214 /*
215 * 8. or try recycling the oldest volume
216 */
217 } else if (jcr->pool->recycle_oldest_volume) {
218 Jmsg(jcr, M_INFO, 0, _("Pruning oldest volume \"%s\"\n"), mr->VolumeName);
219 ok = prune_volume(ua, mr);
220 }
221 free_ua_context(ua);
222 if (ok) {
223 ok = recycle_volume(jcr, mr);
224 Dmsg1(dbglvl, "Recycle after purge oldest=%d\n", ok);
225 }
226 }
227 }
228 }
229 Dmsg2(dbglvl, "VolJobs=%d FirstWritten=%d\n", mr->VolJobs, mr->FirstWritten);
230 if (ok) {
231 /* If we can use the volume, check if it is expired */
232 if (has_volume_expired(jcr, mr)) {
233 if (retry++ < 200) { /* sanity check */
234 continue; /* try again from the top */
235 } else {
236 Jmsg(jcr, M_ERROR, 0, _(
237 "We seem to be looping trying to find the next volume. I give up.\n"));
238 ok = false;
239 }
240 }
241 }
242 break;
243 } /* end for loop */
244 db_unlock(jcr->db);
245 Dmsg1(dbglvl, "return ok=%d find_next_vol\n", ok);
246
247 /* We keep the record of all previous volumes requested */
248 if (ok) {
249 add_volume_to_exclude_list(jcr, index, mr);;
250 }
251 return ok;
252 }
253
254 /*
255 * Check if any time limits or use limits have expired
256 * if so, set the VolStatus appropriately.
257 */
has_volume_expired(JCR * jcr,MEDIA_DBR * mr)258 bool has_volume_expired(JCR *jcr, MEDIA_DBR *mr)
259 {
260 bool expired = false;
261 char ed1[50];
262 /*
263 * Check limits and expirations if "Append" and it has been used
264 * i.e. mr->VolJobs > 0
265 *
266 */
267 if (strcmp(mr->VolStatus, "Append") == 0 && mr->VolJobs > 0) {
268 /* First handle Max Volume Bytes */
269 if ((mr->MaxVolBytes > 0 && mr->VolBytes >= mr->MaxVolBytes)) {
270 Jmsg(jcr, M_INFO, 0, _("Max Volume bytes=%s exceeded. "
271 "Marking Volume \"%s\" as Full.\n"),
272 edit_uint64_with_commas(mr->MaxVolBytes, ed1), mr->VolumeName);
273 bstrncpy(mr->VolStatus, "Full", sizeof(mr->VolStatus));
274 expired = true;
275
276 /* Now see if Volume should only be used once */
277 } else if (mr->VolBytes > 0 && jcr->pool->use_volume_once) {
278 Jmsg(jcr, M_INFO, 0, _("Volume used once. "
279 "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
280 bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
281 expired = true;
282
283 /* Now see if Max Jobs written to volume */
284 } else if (mr->MaxVolJobs > 0 && mr->MaxVolJobs <= mr->VolJobs) {
285 Jmsg(jcr, M_INFO, 0, _("Max Volume jobs=%s exceeded. "
286 "Marking Volume \"%s\" as Used.\n"),
287 edit_uint64_with_commas(mr->MaxVolJobs, ed1), mr->VolumeName);
288 Dmsg3(dbglvl, "MaxVolJobs=%d JobId=%d Vol=%s\n", mr->MaxVolJobs,
289 (uint32_t)jcr->JobId, mr->VolumeName);
290 bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
291 expired = true;
292
293 /* Now see if Max Files written to volume */
294 } else if (mr->MaxVolFiles > 0 && mr->MaxVolFiles <= mr->VolFiles) {
295 Jmsg(jcr, M_INFO, 0, _("Max Volume files=%s exceeded. "
296 "Marking Volume \"%s\" as Used.\n"),
297 edit_uint64_with_commas(mr->MaxVolFiles, ed1), mr->VolumeName);
298 bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
299 expired = true;
300
301 /* Finally, check Use duration expiration */
302 } else if (mr->VolUseDuration > 0) {
303 utime_t now = time(NULL);
304 /* See if Vol Use has expired */
305 if (mr->VolUseDuration <= (now - mr->FirstWritten)) {
306 Jmsg(jcr, M_INFO, 0, _("Max configured use duration=%s sec. exceeded. "
307 "Marking Volume \"%s\" as Used.\n"),
308 edit_uint64_with_commas(mr->VolUseDuration, ed1), mr->VolumeName);
309 bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
310 expired = true;
311 }
312 }
313 }
314
315 /* Check if the Pool quota is respected */
316 if (!expired && use_max_pool_bytes(jcr)) {
317 POOL_DBR pr;
318 bstrncpy(pr.Name, jcr->pool->name(), sizeof(pr.Name));
319 if (has_quota_reached(jcr, &pr)) { /* We can reuse the current value */
320 Jmsg(jcr, M_INFO, 0, _("Max Pool Bytes %sB exceeded. "
321 "Marking Volume \"%s\" as Used.\n"),
322 edit_uint64_with_suffix(pr.MaxPoolBytes, ed1), mr->VolumeName);
323 bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
324 expired = true;
325 }
326 }
327
328 if (expired) {
329 /* Need to update media */
330 Dmsg1(dbglvl, "Vol=%s has expired update media record\n", mr->VolumeName);
331 set_storageid_in_mr(NULL, mr);
332 if (!db_update_media_record(jcr, jcr->db, mr)) {
333 Jmsg(jcr, M_ERROR, 0, _("Catalog error updating volume \"%s\". ERR=%s"),
334 mr->VolumeName, db_strerror(jcr->db));
335 }
336 }
337 Dmsg2(dbglvl, "Vol=%s expired=%d\n", mr->VolumeName, expired);
338 return expired;
339 }
340
341 /*
342 * Try hard to recycle the current volume
343 *
344 * Returns: on failure - reason = NULL
345 * on success - reason - pointer to reason
346 */
check_if_volume_valid_or_recyclable(JCR * jcr,MEDIA_DBR * mr,const char ** reason)347 void check_if_volume_valid_or_recyclable(JCR *jcr, MEDIA_DBR *mr, const char **reason)
348 {
349 int ok;
350
351 *reason = NULL;
352
353 /* Check if a duration or limit has expired */
354 if (has_volume_expired(jcr, mr)) {
355 *reason = _("volume has expired");
356 if (!mr->Recycle) { /* cannot recycle */
357 return;
358 }
359 /* Keep going because we may be able to recycle volume */
360 }
361
362 /*
363 * Now see if we can use the volume as is
364 */
365 if (strcmp(mr->VolStatus, "Append") == 0 ||
366 strcmp(mr->VolStatus, "Recycle") == 0) {
367 *reason = NULL;
368 return;
369 }
370
371 /*
372 * Check if the Volume is already marked for recycling
373 */
374 if (strcmp(mr->VolStatus, "Purged") == 0) {
375 if (recycle_volume(jcr, mr)) {
376 Jmsg(jcr, M_INFO, 0, _("Recycled current volume \"%s\"\n"), mr->VolumeName);
377 *reason = NULL;
378 return;
379 } else {
380 /* In principle this shouldn't happen */
381 *reason = _("and recycling of current volume failed");
382 return;
383 }
384 }
385
386 /* At this point, the volume is not valid for writing */
387 *reason = _("but should be Append, Purged or Recycle");
388
389 /*
390 * What we're trying to do here is see if the current volume is
391 * "recyclable" - ie. if we prune all expired jobs off it, is
392 * it now possible to reuse it for the job that it is currently
393 * needed for?
394 */
395 if (!mr->Recycle) {
396 *reason = _("volume has recycling disabled");
397 return;
398 }
399 /*
400 * Check retention period from last written, but recycle to within
401 * a minute to try to catch close calls ...
402 */
403 if (mr->LastWritten > 0
404 && mr->VolRetention > 0
405 && (mr->LastWritten + mr->VolRetention - 60) < (utime_t)time(NULL)
406 && jcr->pool->recycle_current_volume
407 && (strcmp(mr->VolStatus, "Full") == 0 ||
408 strcmp(mr->VolStatus, "Used") == 0)) {
409 /*
410 * Attempt prune of current volume to see if we can
411 * recycle it for use.
412 */
413 UAContext *ua;
414
415 ua = new_ua_context(jcr);
416 ok = prune_volume(ua, mr);
417 free_ua_context(ua);
418
419 if (ok) {
420 /* If fully purged, recycle current volume */
421 if (recycle_volume(jcr, mr)) {
422 Jmsg(jcr, M_INFO, 0, _("Recycled current volume \"%s\"\n"), mr->VolumeName);
423 *reason = NULL;
424 } else {
425 *reason = _("but should be Append, Purged or Recycle (recycling of the "
426 "current volume failed)");
427 }
428 } else {
429 *reason = _("but should be Append, Purged or Recycle (cannot automatically "
430 "recycle current volume, as it still contains unpruned data "
431 "or the Volume Retention time has not expired.)");
432 }
433 }
434 }
435
436 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
437
get_scratch_volume(JCR * jcr,bool InChanger,MEDIA_DBR * mr,STORE * store)438 bool get_scratch_volume(JCR *jcr, bool InChanger, MEDIA_DBR *mr,
439 STORE *store)
440 {
441 MEDIA_DBR smr; /* for searching scratch pool */
442 POOL_DBR spr;
443 bool ok = false;
444 bool found = false;
445 char ed1[50];
446
447 /* Only one thread at a time can pull from the scratch pool */
448 P(mutex);
449 /*
450 * Get Pool record for Scratch Pool
451 * choose between ScratchPoolId and Scratch
452 * db_get_pool_numvols will first try ScratchPoolId,
453 * and then try the pool named Scratch
454 */
455 bmemset(&spr, 0, sizeof(spr));
456 bstrncpy(spr.Name, "Scratch", sizeof(spr.Name));
457 spr.PoolId = mr->ScratchPoolId;
458 if (db_get_pool_record(jcr, jcr->db, &spr)) {
459 smr.PoolId = spr.PoolId;
460 bstrncpy(smr.VolStatus, "Append", sizeof(smr.VolStatus)); /* want only appendable volumes */
461 bstrncpy(smr.MediaType, mr->MediaType, sizeof(smr.MediaType));
462
463 /*
464 * If we do not find a valid Scratch volume, try
465 * recycling any existing purged volumes, then
466 * try to take the oldest volume.
467 */
468 set_storageid_in_mr(store, &smr); /* put StorageId in new record */
469 if (db_find_next_volume(jcr, jcr->db, 1, InChanger, &smr)) {
470 found = true;
471
472 } else if (find_recycled_volume(jcr, InChanger, &smr, store)) {
473 found = true;
474
475 } else if (recycle_oldest_purged_volume(jcr, InChanger, &smr, store)) {
476 found = true;
477 }
478
479 if (found) {
480 POOL_DBR pr;
481 POOL_MEM query(PM_MESSAGE);
482
483 /*
484 * Get pool record where the Scratch Volume will go to ensure
485 * that we can add a Volume.
486 */
487 bstrncpy(pr.Name, jcr->pool->name(), sizeof(pr.Name));
488 if (use_max_pool_bytes(jcr)) {
489 pr.PoolBytes = 1;
490 }
491
492 if (!db_get_pool_numvols(jcr, jcr->db, &pr)) {
493 Jmsg(jcr, M_WARNING, 0, _("Unable to get Pool record: ERR=%s"),
494 db_strerror(jcr->db));
495 goto bail_out;
496 }
497
498 /* Make sure there is room for another volume */
499 if (pr.MaxVols > 0 && pr.NumVols >= pr.MaxVols) {
500 Jmsg(jcr, M_WARNING, 0, _("Unable add Scratch Volume, Pool \"%s\" full MaxVols=%d\n"),
501 jcr->pool->name(), pr.MaxVols);
502 goto bail_out;
503 }
504
505 /* Make sure there is room for another volume */
506 if (check_max_pool_bytes(&pr)) {
507 Jmsg(jcr, M_WARNING, 0, _("Unable add Scratch Volume, Pool \"%s\" full MaxBytes=%sB\n"),
508 jcr->pool->name(), edit_uint64_with_suffix(pr.MaxPoolBytes, ed1));
509 goto bail_out;
510 }
511
512 mr->copy(&smr);
513 set_storageid_in_mr(store, mr);
514
515 /* Set default parameters from current pool */
516 set_pool_dbr_defaults_in_media_dbr(mr, &pr);
517
518 /*
519 * set_pool_dbr_defaults_in_media_dbr set VolStatus to Append,
520 * we could have Recycled media, also, we retain the old
521 * RecyclePoolId.
522 */
523 bstrncpy(mr->VolStatus, smr.VolStatus, sizeof(smr.VolStatus));
524 mr->RecyclePoolId = smr.RecyclePoolId;
525
526 if (!db_update_media_record(jcr, jcr->db, mr)) {
527 Jmsg(jcr, M_WARNING, 0, _("Failed to move Scratch Volume. ERR=%s\n"),
528 db_strerror(jcr->db));
529 goto bail_out;
530 }
531
532 Jmsg(jcr, M_INFO, 0, _("Using Volume \"%s\" from '%s' %spool.\n"),
533 mr->VolumeName, spr.Name,
534 ((strcmp(spr.Name, "Scratch") == 0) ? "" : "Scratch "));
535
536 ok = true;
537 }
538 }
539 bail_out:
540 V(mutex);
541 return ok;
542 }
543