]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/next_vol.c
Implement code to turn off pruning in obtaining the next volume.
[bacula/bacula] / bacula / src / dird / next_vol.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2001-2007 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU General Public
10    License as published by the Free Software Foundation plus additions
11    that are listed in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of John Walker.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *
30  *   Bacula Director -- next_vol -- handles finding the next
31  *    volume for append.  Split out of catreq.c August MMIII
32  *    catalog request from the Storage daemon.
33
34  *     Kern Sibbald, March MMI
35  *
36  *   Version $Id$
37  */
38
39 #include "bacula.h"
40 #include "dird.h"
41
42 static bool get_scratch_volume(JCR *jcr, MEDIA_DBR *mr, bool InChanger);
43
44
45 /*
46  *  Items needed:
47  *   mr.PoolId must be set
48  *   jcr->wstore
49  *   jcr->db
50  *   jcr->pool
51  *   MEDIA_DBR mr with PoolId set
52  *   create -- whether or not to create a new volume
53  */
54 int find_next_volume_for_append(JCR *jcr, MEDIA_DBR *mr, int index,             
55                                 bool create, bool prune)
56 {
57    int retry = 0;
58    bool ok;
59    bool InChanger;
60    STORE *store = jcr->wstore;
61
62    bstrncpy(mr->MediaType, store->media_type, sizeof(mr->MediaType));
63    Dmsg2(150, "find_next_vol_for_append: PoolId=%d, MediaType=%s\n", (int)mr->PoolId, mr->MediaType);
64    /*
65     * If we are using an Autochanger, restrict Volume
66     *   search to the Autochanger on the first pass
67     */
68    InChanger = store->autochanger;
69    /*
70     * Find the Next Volume for Append
71     */
72    db_lock(jcr->db);
73    for ( ;; ) {
74       bstrncpy(mr->VolStatus, "Append", sizeof(mr->VolStatus));  /* want only appendable volumes */
75       /*
76        *  1. Look for volume with "Append" status.
77        */
78       ok = db_find_next_volume(jcr, jcr->db, index, InChanger, mr);
79
80       if (!ok) {
81          Dmsg4(050, "after find_next_vol ok=%d index=%d InChanger=%d Vstat=%s\n",
82                ok, index, InChanger, mr->VolStatus);
83          /*
84           * 2. Try finding a recycled volume
85           */
86          ok = find_recycled_volume(jcr, InChanger, mr);
87          Dmsg2(150, "find_recycled_volume ok=%d FW=%d\n", ok, mr->FirstWritten);
88          if (!ok) {
89             /*
90              * 3. Try recycling any purged volume
91              */
92             ok = recycle_oldest_purged_volume(jcr, InChanger, mr);
93             if (!ok) {
94                /*
95                 * 4. Try pruning Volumes
96                 */
97                Dmsg0(150, "Call prune_volumes\n");
98                if (prune) {
99                   prune_volumes(jcr, mr);
100                }
101                ok = recycle_oldest_purged_volume(jcr, InChanger, mr);
102                if (!ok) {
103                   Dmsg4(050, "after prune volumes_vol ok=%d index=%d InChanger=%d Vstat=%s\n",
104                         ok, index, InChanger, mr->VolStatus);
105                   /*
106                    * 5. Try pulling a volume from the Scratch pool
107                    */ 
108                   ok = get_scratch_volume(jcr, mr, InChanger);
109                }
110                /*
111                 * If we are using an Autochanger and have not found
112                 * a volume, retry looking for any volume. 
113                 */
114                if (InChanger) {
115                   InChanger = false;
116                   if (!ok) {
117                      continue;           /* retry again accepting any volume */
118                   }
119                }
120             }
121          }
122
123
124          if (!ok && create) {
125             /*
126              * 6. Try "creating" a new Volume
127              */
128             ok = newVolume(jcr, mr);
129          }
130          /*
131           *  Look at more drastic ways to find an Appendable Volume
132           */
133          if (!ok && (jcr->pool->purge_oldest_volume ||
134                      jcr->pool->recycle_oldest_volume)) {
135             Dmsg2(200, "No next volume found. PurgeOldest=%d\n RecyleOldest=%d",
136                 jcr->pool->purge_oldest_volume, jcr->pool->recycle_oldest_volume);
137             /* Find oldest volume to recycle */
138             ok = db_find_next_volume(jcr, jcr->db, -1, InChanger, mr);
139             Dmsg1(400, "Find oldest=%d\n", ok);
140             if (ok && prune) {
141                UAContext *ua;
142                Dmsg0(400, "Try purge.\n");
143                /*
144                 * 7.  Try to purging oldest volume only if not UA calling us.
145                 */
146                ua = new_ua_context(jcr);
147                if (jcr->pool->purge_oldest_volume && create) {
148                   Jmsg(jcr, M_INFO, 0, _("Purging oldest volume \"%s\"\n"), mr->VolumeName);
149                   ok = purge_jobs_from_volume(ua, mr);
150                /*
151                 * 8. or try recycling the oldest volume
152                 */
153                } else if (jcr->pool->recycle_oldest_volume) {
154                   Jmsg(jcr, M_INFO, 0, _("Pruning oldest volume \"%s\"\n"), mr->VolumeName);
155                   ok = prune_volume(ua, mr);
156                }
157                free_ua_context(ua);
158                if (ok) {
159                   ok = recycle_volume(jcr, mr);
160                   Dmsg1(400, "Recycle after purge oldest=%d\n", ok);
161                }
162             }
163          }
164       }
165       Dmsg2(100, "VolJobs=%d FirstWritten=%d\n", mr->VolJobs, mr->FirstWritten);
166       if (ok) {
167          /* If we can use the volume, check if it is expired */
168          if (has_volume_expired(jcr, mr)) {
169             if (retry++ < 200) {            /* sanity check */
170                continue;                    /* try again from the top */
171             } else {
172                Jmsg(jcr, M_ERROR, 0, _(
173 "We seem to be looping trying to find the next volume. I give up.\n"));
174             }
175          }
176       }
177       break;
178    } /* end for loop */
179    db_unlock(jcr->db);
180    Dmsg1(150, "return ok=%d find_next_vol\n", ok);
181    return ok;
182 }
183
184 /*
185  * Check if any time limits or use limits have expired
186  *   if so, set the VolStatus appropriately.
187  */
188 bool has_volume_expired(JCR *jcr, MEDIA_DBR *mr)
189 {
190    bool expired = false;
191    /*
192     * Check limits and expirations if "Append" and it has been used
193     * i.e. mr->VolJobs > 0
194     *
195     */
196    if (strcmp(mr->VolStatus, "Append") == 0 && mr->VolJobs > 0) {
197       /* First handle Max Volume Bytes */
198       if ((mr->MaxVolBytes > 0 && mr->VolBytes >= mr->MaxVolBytes)) {
199          Jmsg(jcr, M_INFO, 0, _("Max Volume bytes exceeded. "
200              "Marking Volume \"%s\" as Full.\n"), mr->VolumeName);
201          bstrncpy(mr->VolStatus, "Full", sizeof(mr->VolStatus));
202          expired = true;
203
204       /* Now see if Volume should only be used once */
205       } else if (mr->VolBytes > 0 && jcr->pool->use_volume_once) {
206          Jmsg(jcr, M_INFO, 0, _("Volume used once. "
207              "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
208          bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
209          expired = true;
210
211       /* Now see if Max Jobs written to volume */
212       } else if (mr->MaxVolJobs > 0 && mr->MaxVolJobs <= mr->VolJobs) {
213          Jmsg(jcr, M_INFO, 0, _("Max Volume jobs exceeded. "
214              "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
215          bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
216          expired = true;
217
218       /* Now see if Max Files written to volume */
219       } else if (mr->MaxVolFiles > 0 && mr->MaxVolFiles <= mr->VolFiles) {
220          Jmsg(jcr, M_INFO, 0, _("Max Volume files exceeded. "
221              "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
222          bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
223          expired = true;
224
225       /* Finally, check Use duration expiration */
226       } else if (mr->VolUseDuration > 0) {
227          utime_t now = time(NULL);
228          /* See if Vol Use has expired */
229          if (mr->VolUseDuration <= (now - mr->FirstWritten)) {
230             Jmsg(jcr, M_INFO, 0, _("Max configured use duration exceeded. "
231                "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
232             bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
233             expired = true;
234          }
235       }
236    }
237    if (expired) {
238       /* Need to update media */
239       if (!db_update_media_record(jcr, jcr->db, mr)) {
240          Jmsg(jcr, M_ERROR, 0, _("Catalog error updating volume \"%s\". ERR=%s"),
241               mr->VolumeName, db_strerror(jcr->db));
242       }
243    }
244    return expired;
245 }
246
247 /*
248  * Try hard to recycle the current volume
249  *
250  *  Returns: on failure - reason = NULL
251  *           on success - reason - pointer to reason
252  */
253 void check_if_volume_valid_or_recyclable(JCR *jcr, MEDIA_DBR *mr, const char **reason)
254 {
255    int ok;
256
257    *reason = NULL;
258
259    /*  Check if a duration or limit has expired */
260    if (has_volume_expired(jcr, mr)) {
261       *reason = _("volume has expired");
262       /* Keep going because we may be able to recycle volume */
263    }
264
265    /*
266     * Now see if we can use the volume as is
267     */
268    if (strcmp(mr->VolStatus, "Append") == 0 ||
269        strcmp(mr->VolStatus, "Recycle") == 0) {
270       *reason = NULL;
271       return;
272    }
273
274    /*
275     * Check if the Volume is already marked for recycling
276     */
277    if (strcmp(mr->VolStatus, "Purged") == 0) {
278       if (recycle_volume(jcr, mr)) {
279          Jmsg(jcr, M_INFO, 0, _("Recycled current volume \"%s\"\n"), mr->VolumeName);
280          *reason = NULL;
281          return;
282       } else {
283          /* In principle this shouldn't happen */
284          *reason = _("and recycling of current volume failed");
285          return;
286       }
287    }
288
289    /* At this point, the volume is not valid for writing */
290    *reason = _("but should be Append, Purged or Recycle");
291
292    /*
293     * What we're trying to do here is see if the current volume is
294     * "recyclable" - ie. if we prune all expired jobs off it, is
295     * it now possible to reuse it for the job that it is currently
296     * needed for?
297     */
298    if ((mr->LastWritten + mr->VolRetention) < (utime_t)time(NULL)
299          && mr->Recycle && jcr->pool->recycle_current_volume
300          && (strcmp(mr->VolStatus, "Full") == 0 ||
301             strcmp(mr->VolStatus, "Used") == 0)) {
302       /*
303        * Attempt prune of current volume to see if we can
304        * recycle it for use.
305        */
306       UAContext *ua;
307
308       ua = new_ua_context(jcr);
309       ok = prune_volume(ua, mr);
310       free_ua_context(ua);
311
312       if (ok) {
313          /* If fully purged, recycle current volume */
314          if (recycle_volume(jcr, mr)) {
315             Jmsg(jcr, M_INFO, 0, _("Recycled current volume \"%s\"\n"), mr->VolumeName);
316             *reason = NULL;
317          } else {
318             *reason = _("but should be Append, Purged or Recycle (recycling of the "
319                "current volume failed)");
320          }
321       } else {
322          *reason = _("but should be Append, Purged or Recycle (cannot automatically "
323             "recycle current volume, as it still contains unpruned data "
324             "or the Volume Retention time has not expired.)");
325       }
326    }
327 }
328
329 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
330
331 static bool get_scratch_volume(JCR *jcr, MEDIA_DBR *mr, bool InChanger)
332 {
333    MEDIA_DBR smr;
334    POOL_DBR spr, pr;
335    bool ok = false;
336    bool found = false;
337    char ed1[50], ed2[50];
338
339    /* Only one thread at a time can pull from the scratch pool */
340    P(mutex);
341    /* 
342     * Get Pool record for Scratch Pool
343     */
344    memset(&spr, 0, sizeof(spr));
345    bstrncpy(spr.Name, "Scratch", sizeof(spr.Name));
346    if (db_get_pool_record(jcr, jcr->db, &spr)) {
347       memset(&smr, 0, sizeof(smr));
348       smr.PoolId = spr.PoolId;
349       if (InChanger) {       
350          smr.StorageId = mr->StorageId;  /* want only Scratch Volumes in changer */
351       }
352       bstrncpy(smr.VolStatus, "Append", sizeof(smr.VolStatus));  /* want only appendable volumes */
353       bstrncpy(smr.MediaType, mr->MediaType, sizeof(smr.MediaType));
354
355       /*
356        * If we do not find a valid Scratch volume, try
357        *  recycling any existing purged volumes, then
358        *  try to take the oldest volume.
359        */
360       if (db_find_next_volume(jcr, jcr->db, 1, InChanger, &smr)) {
361          found = true;
362
363       } else if (find_recycled_volume(jcr, InChanger, &smr)) {
364          found = true;
365
366       } else if (recycle_oldest_purged_volume(jcr, InChanger, &smr)) {
367          found = true;
368       }
369
370       if (found) {
371          POOL_MEM query(PM_MESSAGE);
372
373          /*   
374           * Get pool record where the Scratch Volume will go to ensure
375           * that we can add a Volume.
376           */
377          memset(&pr, 0, sizeof(pr));
378          bstrncpy(pr.Name, jcr->pool->hdr.name, sizeof(pr.Name));
379          if (!db_get_pool_record(jcr, jcr->db, &pr)) {
380             Jmsg(jcr, M_WARNING, 0, _("Unable to get Pool record: ERR=%s"), 
381                  db_strerror(jcr->db));
382             goto bail_out;
383          }
384          if (pr.MaxVols > 0 && pr.NumVols >= pr.MaxVols) {
385             Jmsg(jcr, M_WARNING, 0, _("Unable add Scratch Volume, Pool \"%s\" full MaxVols=%d\n"),
386                jcr->pool->hdr.name, pr.MaxVols);
387             goto bail_out;
388          }
389
390          /* OK, now move Scratch Volume */
391          db_lock(jcr->db);
392          Mmsg(query, "UPDATE Media SET PoolId=%s WHERE MediaId=%s",
393               edit_int64(mr->PoolId, ed1),
394               edit_int64(smr.MediaId, ed2));
395          ok = db_sql_query(jcr->db, query.c_str(), NULL, NULL);  
396          db_unlock(jcr->db);
397          if (!ok) {
398             Jmsg(jcr, M_WARNING, 0, _("Failed to move Scratch Volume. ERR=%s\n"),
399                db_strerror(jcr->db));
400            goto bail_out;
401           }
402          Jmsg(jcr, M_INFO, 0, _("Using Volume \"%s\" from 'Scratch' pool.\n"), 
403               smr.VolumeName);
404          /* Set new Pool Id in smr record, then copy it to mr */
405          smr.PoolId = mr->PoolId;
406          memcpy(mr, &smr, sizeof(MEDIA_DBR));
407          /* Set default parameters from current pool */
408          set_pool_dbr_defaults_in_media_dbr(mr, &pr);
409          /* set_pool_dbr_defaults_in_media_dbr set VolStatus to Append,
410           * we could have Recycled media */
411          bstrncpy(mr->VolStatus, smr.VolStatus, sizeof(smr.VolStatus));
412          if (!db_update_media_record(jcr, jcr->db, mr)) {
413             Jmsg(jcr, M_WARNING, 0, _("Unable to update Volume record: ERR=%s"), 
414                  db_strerror(jcr->db));
415             ok = false;
416          }
417       }
418    }
419 bail_out:
420    V(mutex);
421    return ok;
422 }