]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/next_vol.c
kes Write new subroutine is_volume_purged() that explicitly checks
[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, bool create)
55 {
56    int retry = 0;
57    bool ok;
58    bool InChanger;
59    STORE *store = jcr->wstore;
60
61    bstrncpy(mr->MediaType, store->media_type, sizeof(mr->MediaType));
62    Dmsg2(150, "find_next_vol_for_append: PoolId=%d, MediaType=%s\n", (int)mr->PoolId, mr->MediaType);
63    /*
64     * If we are using an Autochanger, restrict Volume
65     *   search to the Autochanger on the first pass
66     */
67    InChanger = store->autochanger;
68    /*
69     * Find the Next Volume for Append
70     */
71    db_lock(jcr->db);
72    for ( ;; ) {
73       bstrncpy(mr->VolStatus, "Append", sizeof(mr->VolStatus));  /* want only appendable volumes */
74       /*
75        *  1. Look for volume with "Append" status.
76        */
77       ok = db_find_next_volume(jcr, jcr->db, index, InChanger, mr);
78
79       if (!ok) {
80          Dmsg4(050, "after find_next_vol ok=%d index=%d InChanger=%d Vstat=%s\n",
81                ok, index, InChanger, mr->VolStatus);
82          /*
83           * 2. Try finding a recycled volume
84           */
85          ok = find_recycled_volume(jcr, InChanger, mr);
86          Dmsg2(150, "find_recycled_volume ok=%d FW=%d\n", ok, mr->FirstWritten);
87          if (!ok) {
88             /*
89              * 3. Try recycling any purged volume
90              */
91             ok = recycle_oldest_purged_volume(jcr, InChanger, mr);
92             if (!ok) {
93                /*
94                 * 4. Try pruning Volumes
95                 */
96                Dmsg0(150, "Call prune_volumes\n");
97                prune_volumes(jcr, mr);
98                ok = recycle_oldest_purged_volume(jcr, InChanger, mr);
99                if (!ok) {
100                   Dmsg4(050, "after prune volumes_vol ok=%d index=%d InChanger=%d Vstat=%s\n",
101                         ok, index, InChanger, mr->VolStatus);
102                   /*
103                    * 5. Try pulling a volume from the Scratch pool
104                    */ 
105                   ok = get_scratch_volume(jcr, mr, InChanger);
106                }
107                /*
108                 * If we are using an Autochanger and have not found
109                 * a volume, retry looking for any volume. 
110                 */
111                if (InChanger) {
112                   InChanger = false;
113                   if (!ok) {
114                      continue;           /* retry again accepting any volume */
115                   }
116                }
117             }
118          }
119
120
121          if (!ok && create) {
122             /*
123              * 6. Try "creating" a new Volume
124              */
125             ok = newVolume(jcr, mr);
126          }
127          /*
128           *  Look at more drastic ways to find an Appendable Volume
129           */
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);
137             if (ok) {
138                UAContext *ua;
139                Dmsg0(400, "Try purge.\n");
140                /*
141                 * 7.  Try to purging oldest volume only if not UA calling us.
142                 */
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);
147                /*
148                 * 8. or try recycling the oldest volume
149                 */
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);
153                }
154                free_ua_context(ua);
155                if (ok) {
156                   ok = recycle_volume(jcr, mr);
157                   Dmsg1(400, "Recycle after purge oldest=%d\n", ok);
158                }
159             }
160          }
161       }
162       Dmsg2(100, "VolJobs=%d FirstWritten=%d\n", mr->VolJobs, mr->FirstWritten);
163       if (ok) {
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 */
168             } else {
169                Jmsg(jcr, M_ERROR, 0, _(
170 "We seem to be looping trying to find the next volume. I give up.\n"));
171             }
172          }
173       }
174       break;
175    } /* end for loop */
176    db_unlock(jcr->db);
177    Dmsg1(150, "return ok=%d find_next_vol\n", ok);
178    return ok;
179 }
180
181 /*
182  * Check if any time limits or use limits have expired
183  *   if so, set the VolStatus appropriately.
184  */
185 bool has_volume_expired(JCR *jcr, MEDIA_DBR *mr)
186 {
187    bool expired = false;
188    /*
189     * Check limits and expirations if "Append" and it has been used
190     * i.e. mr->VolJobs > 0
191     *
192     */
193    if (strcmp(mr->VolStatus, "Append") == 0 && mr->VolJobs > 0) {
194       /* First handle Max Volume Bytes */
195       if ((mr->MaxVolBytes > 0 && mr->VolBytes >= mr->MaxVolBytes)) {
196          Jmsg(jcr, M_INFO, 0, _("Max Volume bytes exceeded. "
197              "Marking Volume \"%s\" as Full.\n"), mr->VolumeName);
198          bstrncpy(mr->VolStatus, "Full", sizeof(mr->VolStatus));
199          expired = true;
200
201       /* Now see if Volume should only be used once */
202       } else if (mr->VolBytes > 0 && jcr->pool->use_volume_once) {
203          Jmsg(jcr, M_INFO, 0, _("Volume used once. "
204              "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
205          bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
206          expired = true;
207
208       /* Now see if Max Jobs written to volume */
209       } else if (mr->MaxVolJobs > 0 && mr->MaxVolJobs <= mr->VolJobs) {
210          Jmsg(jcr, M_INFO, 0, _("Max Volume jobs exceeded. "
211              "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
212          bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
213          expired = true;
214
215       /* Now see if Max Files written to volume */
216       } else if (mr->MaxVolFiles > 0 && mr->MaxVolFiles <= mr->VolFiles) {
217          Jmsg(jcr, M_INFO, 0, _("Max Volume files exceeded. "
218              "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
219          bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
220          expired = true;
221
222       /* Finally, check Use duration expiration */
223       } else if (mr->VolUseDuration > 0) {
224          utime_t now = time(NULL);
225          /* See if Vol Use has expired */
226          if (mr->VolUseDuration <= (now - mr->FirstWritten)) {
227             Jmsg(jcr, M_INFO, 0, _("Max configured use duration exceeded. "
228                "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
229             bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
230             expired = true;
231          }
232       }
233    }
234    if (expired) {
235       /* Need to update media */
236       if (!db_update_media_record(jcr, jcr->db, mr)) {
237          Jmsg(jcr, M_ERROR, 0, _("Catalog error updating volume \"%s\". ERR=%s"),
238               mr->VolumeName, db_strerror(jcr->db));
239       }
240    }
241    return expired;
242 }
243
244 /*
245  * Try hard to recycle the current volume
246  *
247  *  Returns: on failure - reason = NULL
248  *           on success - reason - pointer to reason
249  */
250 void check_if_volume_valid_or_recyclable(JCR *jcr, MEDIA_DBR *mr, const char **reason)
251 {
252    int ok;
253
254    *reason = NULL;
255
256    /*  Check if a duration or limit has expired */
257    if (has_volume_expired(jcr, mr)) {
258       *reason = _("volume has expired");
259       /* Keep going because we may be able to recycle volume */
260    }
261
262    /*
263     * Now see if we can use the volume as is
264     */
265    if (strcmp(mr->VolStatus, "Append") == 0 ||
266        strcmp(mr->VolStatus, "Recycle") == 0) {
267       *reason = NULL;
268       return;
269    }
270
271    /*
272     * Check if the Volume is already marked for recycling
273     */
274    if (strcmp(mr->VolStatus, "Purged") == 0) {
275       if (recycle_volume(jcr, mr)) {
276          Jmsg(jcr, M_INFO, 0, _("Recycled current volume \"%s\"\n"), mr->VolumeName);
277          *reason = NULL;
278          return;
279       } else {
280          /* In principle this shouldn't happen */
281          *reason = _("and recycling of current volume failed");
282          return;
283       }
284    }
285
286    /* At this point, the volume is not valid for writing */
287    *reason = _("but should be Append, Purged or Recycle");
288
289    /*
290     * What we're trying to do here is see if the current volume is
291     * "recyclable" - ie. if we prune all expired jobs off it, is
292     * it now possible to reuse it for the job that it is currently
293     * needed for?
294     */
295    if ((mr->LastWritten + mr->VolRetention) < (utime_t)time(NULL)
296          && mr->Recycle && jcr->pool->recycle_current_volume
297          && (strcmp(mr->VolStatus, "Full") == 0 ||
298             strcmp(mr->VolStatus, "Used") == 0)) {
299       /*
300        * Attempt prune of current volume to see if we can
301        * recycle it for use.
302        */
303       UAContext *ua;
304
305       ua = new_ua_context(jcr);
306       ok = prune_volume(ua, mr);
307       free_ua_context(ua);
308
309       if (ok) {
310          /* If fully purged, recycle current volume */
311          if (recycle_volume(jcr, mr)) {
312             Jmsg(jcr, M_INFO, 0, _("Recycled current volume \"%s\"\n"), mr->VolumeName);
313             *reason = NULL;
314          } else {
315             *reason = _("but should be Append, Purged or Recycle (recycling of the "
316                "current volume failed)");
317          }
318       } else {
319          *reason = _("but should be Append, Purged or Recycle (cannot automatically "
320             "recycle current volume, as it still contains unpruned data "
321             "or the Volume Retention time has not expired.)");
322       }
323    }
324 }
325
326 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
327
328 static bool get_scratch_volume(JCR *jcr, MEDIA_DBR *mr, bool InChanger)
329 {
330    MEDIA_DBR smr;
331    POOL_DBR spr, pr;
332    bool ok = false;
333    bool found = false;
334    char ed1[50], ed2[50];
335
336    /* Only one thread at a time can pull from the scratch pool */
337    P(mutex);
338    /* 
339     * Get Pool record for Scratch Pool
340     */
341    memset(&spr, 0, sizeof(spr));
342    bstrncpy(spr.Name, "Scratch", sizeof(spr.Name));
343    if (db_get_pool_record(jcr, jcr->db, &spr)) {
344       memset(&smr, 0, sizeof(smr));
345       smr.PoolId = spr.PoolId;
346       if (InChanger) {       
347          smr.StorageId = mr->StorageId;  /* want only Scratch Volumes in changer */
348       }
349       bstrncpy(smr.VolStatus, "Append", sizeof(smr.VolStatus));  /* want only appendable volumes */
350       bstrncpy(smr.MediaType, mr->MediaType, sizeof(smr.MediaType));
351
352       /*
353        * If we do not find a valid Scratch volume, try
354        *  recycling any existing purged volumes, then
355        *  try to take the oldest volume.
356        */
357       if (db_find_next_volume(jcr, jcr->db, 1, InChanger, &smr)) {
358          found = true;
359
360       } else if (find_recycled_volume(jcr, InChanger, &smr)) {
361          found = true;
362
363       } else if (recycle_oldest_purged_volume(jcr, InChanger, &smr)) {
364          found = true;
365       }
366
367       if (found) {
368          POOL_MEM query(PM_MESSAGE);
369
370          /*   
371           * Get pool record where the Scratch Volume will go to ensure
372           * that we can add a Volume.
373           */
374          memset(&pr, 0, sizeof(pr));
375          bstrncpy(pr.Name, jcr->pool->hdr.name, sizeof(pr.Name));
376          if (!db_get_pool_record(jcr, jcr->db, &pr)) {
377             Jmsg(jcr, M_WARNING, 0, _("Unable to get Pool record: ERR=%s"), 
378                  db_strerror(jcr->db));
379             goto bail_out;
380          }
381          if (pr.MaxVols > 0 && pr.NumVols >= pr.MaxVols) {
382             Jmsg(jcr, M_WARNING, 0, _("Unable add Scratch Volume, Pool \"%s\" full MaxVols=%d\n"),
383                jcr->pool->hdr.name, pr.MaxVols);
384             goto bail_out;
385          }
386
387          /* OK, now move Scratch Volume */
388          db_lock(jcr->db);
389          Mmsg(query, "UPDATE Media SET PoolId=%s WHERE MediaId=%s",
390               edit_int64(mr->PoolId, ed1),
391               edit_int64(smr.MediaId, ed2));
392          ok = db_sql_query(jcr->db, query.c_str(), NULL, NULL);  
393          db_unlock(jcr->db);
394          if (!ok) {
395             Jmsg(jcr, M_WARNING, 0, _("Failed to move Scratch Volume. ERR=%s\n"),
396                db_strerror(jcr->db));
397            goto bail_out;
398           }
399          Jmsg(jcr, M_INFO, 0, _("Using Volume \"%s\" from 'Scratch' pool.\n"), 
400               smr.VolumeName);
401          /* Set new Pool Id in smr record, then copy it to mr */
402          smr.PoolId = mr->PoolId;
403          memcpy(mr, &smr, sizeof(MEDIA_DBR));
404          /* Set default parameters from current pool */
405          set_pool_dbr_defaults_in_media_dbr(mr, &pr);
406          /* set_pool_dbr_defaults_in_media_dbr set VolStatus to Append,
407           * we could have Recycled media */
408          bstrncpy(mr->VolStatus, smr.VolStatus, sizeof(smr.VolStatus));
409          if (!db_update_media_record(jcr, jcr->db, mr)) {
410             Jmsg(jcr, M_WARNING, 0, _("Unable to update Volume record: ERR=%s"), 
411                  db_strerror(jcr->db));
412             ok = false;
413          }
414       }
415    }
416 bail_out:
417    V(mutex);
418    return ok;
419 }