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