]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/next_vol.c
Modify find_next_volume() to return all values in Media record.
[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 and included
11    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 /*
43  *  Items needed:
44  *   mr.PoolId must be set
45  *   jcr->wstore
46  *   jcr->db
47  *   jcr->pool
48  *   MEDIA_DBR mr with PoolId set
49  *   create -- whether or not to create a new volume
50  */
51 int find_next_volume_for_append(JCR *jcr, MEDIA_DBR *mr, int index,             
52                                 bool create, bool prune)
53 {
54    int retry = 0;
55    bool ok;
56    bool InChanger;
57    STORE *store = jcr->wstore;
58
59    bstrncpy(mr->MediaType, store->media_type, sizeof(mr->MediaType));
60    Dmsg2(150, "find_next_vol_for_append: PoolId=%d, MediaType=%s\n", (int)mr->PoolId, mr->MediaType);
61    /*
62     * If we are using an Autochanger, restrict Volume
63     *   search to the Autochanger on the first pass
64     */
65    InChanger = store->autochanger;
66    /*
67     * Find the Next Volume for Append
68     */
69    db_lock(jcr->db);
70    for ( ;; ) {
71       bstrncpy(mr->VolStatus, "Append", sizeof(mr->VolStatus));  /* want only appendable volumes */
72       /*
73        *  1. Look for volume with "Append" status.
74        */
75       ok = db_find_next_volume(jcr, jcr->db, index, InChanger, mr);
76
77       if (!ok) {
78          Dmsg4(050, "after find_next_vol ok=%d index=%d InChanger=%d Vstat=%s\n",
79                ok, index, InChanger, mr->VolStatus);
80          /*
81           * 2. Try finding a recycled volume
82           */
83          ok = find_recycled_volume(jcr, InChanger, mr);
84          Dmsg2(150, "find_recycled_volume ok=%d FW=%d\n", ok, mr->FirstWritten);
85          if (!ok) {
86             /*
87              * 3. Try recycling any purged volume
88              */
89             ok = recycle_oldest_purged_volume(jcr, InChanger, mr);
90             if (!ok) {
91                /*
92                 * 4. Try pruning Volumes
93                 */
94                if (prune) {
95                   Dmsg0(150, "Call prune_volumes\n");
96                   prune_volumes(jcr, InChanger, mr);
97                }
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, InChanger, mr);
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 && prune) {
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 bool get_scratch_volume(JCR *jcr, bool InChanger, MEDIA_DBR *mr)
329 {
330    MEDIA_DBR smr;                        /* for searching scratch pool */
331    POOL_DBR spr, pr;
332    bool ok = false;
333    bool found = false;
334
335    /* Only one thread at a time can pull from the scratch pool */
336    P(mutex);
337    /* 
338     * Get Pool record for Scratch Pool
339     */
340    memset(&spr, 0, sizeof(spr));
341    bstrncpy(spr.Name, "Scratch", sizeof(spr.Name));
342    if (db_get_pool_record(jcr, jcr->db, &spr)) {
343       memset(&smr, 0, sizeof(smr));
344       smr.PoolId = spr.PoolId;
345       if (InChanger) {       
346          smr.StorageId = mr->StorageId;  /* want only Scratch Volumes in changer */
347       }
348       bstrncpy(smr.VolStatus, "Append", sizeof(smr.VolStatus));  /* want only appendable volumes */
349       bstrncpy(smr.MediaType, mr->MediaType, sizeof(smr.MediaType));
350
351       /*
352        * If we do not find a valid Scratch volume, try
353        *  recycling any existing purged volumes, then
354        *  try to take the oldest volume.
355        */
356       if (db_find_next_volume(jcr, jcr->db, 1, InChanger, &smr)) {
357          found = true;
358
359       } else if (find_recycled_volume(jcr, InChanger, &smr)) {
360          found = true;
361
362       } else if (recycle_oldest_purged_volume(jcr, InChanger, &smr)) {
363          found = true;
364       }
365
366       if (found) {
367          POOL_MEM query(PM_MESSAGE);
368
369          /*   
370           * Get pool record where the Scratch Volume will go to ensure
371           * that we can add a Volume.
372           */
373          memset(&pr, 0, sizeof(pr));
374          bstrncpy(pr.Name, jcr->pool->name(), sizeof(pr.Name));
375
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          
382          /* Make sure there is room for another volume */
383          if (pr.MaxVols > 0 && pr.NumVols >= pr.MaxVols) {
384             Jmsg(jcr, M_WARNING, 0, _("Unable add Scratch Volume, Pool \"%s\" full MaxVols=%d\n"),
385                  jcr->pool->name(), pr.MaxVols);
386             goto bail_out;
387          }
388
389          memcpy(mr, &smr, sizeof(MEDIA_DBR)); 
390
391          /* Set default parameters from current pool */
392          set_pool_dbr_defaults_in_media_dbr(mr, &pr);
393
394          /*
395           * set_pool_dbr_defaults_in_media_dbr set VolStatus to Append,
396           *   we could have Recycled media, also, we retain the old
397           *   RecyclePoolId.
398           */
399          bstrncpy(mr->VolStatus, smr.VolStatus, sizeof(smr.VolStatus));
400          mr->RecyclePoolId = smr.RecyclePoolId;
401
402          if (!db_update_media_record(jcr, jcr->db, mr)) {
403             Jmsg(jcr, M_WARNING, 0, _("Failed to move Scratch Volume. ERR=%s\n"),
404                  db_strerror(jcr->db));
405             goto bail_out;
406          }
407
408          Jmsg(jcr, M_INFO, 0, _("Using Volume \"%s\" from 'Scratch' pool.\n"), 
409               mr->VolumeName);
410          
411          ok = true;
412       }
413    }
414 bail_out:
415    V(mutex);
416    return ok;
417 }