]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/next_vol.c
kes Generally clean up the manual tape loading code. The main
[bacula/bacula] / bacula / src / dird / next_vol.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2001-2008 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    Dmsg3(100, "find_next_vol_for_append: JobId=%u PoolId=%d, MediaType=%s\n", 
61          (uint32_t)jcr->JobId, (int)mr->PoolId, mr->MediaType);
62    /*
63     * If we are using an Autochanger, restrict Volume
64     *   search to the Autochanger on the first pass
65     */
66    InChanger = store->autochanger;
67    /*
68     * Find the Next Volume for Append
69     */
70    db_lock(jcr->db);
71    for ( ;; ) {
72       bstrncpy(mr->VolStatus, "Append", sizeof(mr->VolStatus));  /* want only appendable volumes */
73       /*
74        *  1. Look for volume with "Append" status.
75        */
76       ok = db_find_next_volume(jcr, jcr->db, index, InChanger, mr);
77
78       if (!ok) {
79          Dmsg4(050, "after find_next_vol ok=%d index=%d InChanger=%d Vstat=%s\n",
80                ok, index, InChanger, mr->VolStatus);
81          /*
82           * 2. Try finding a recycled volume
83           */
84          ok = find_recycled_volume(jcr, InChanger, mr);
85          Dmsg2(150, "find_recycled_volume ok=%d FW=%d\n", ok, mr->FirstWritten);
86          if (!ok) {
87             /*
88              * 3. Try recycling any purged volume
89              */
90             ok = recycle_oldest_purged_volume(jcr, InChanger, mr);
91             if (!ok) {
92                /*
93                 * 4. Try pruning Volumes
94                 */
95                if (prune) {
96                   Dmsg0(150, "Call prune_volumes\n");
97                   prune_volumes(jcr, InChanger, mr);
98                }
99                ok = recycle_oldest_purged_volume(jcr, InChanger, mr);
100                if (!ok && create) {
101                   Dmsg4(050, "after prune volumes_vol ok=%d index=%d InChanger=%d Vstat=%s\n",
102                         ok, index, InChanger, mr->VolStatus);
103                   /*
104                    * 5. Try pulling a volume from the Scratch pool
105                    */ 
106                   ok = get_scratch_volume(jcr, InChanger, mr);
107                }
108                /*
109                 * If we are using an Autochanger and have not found
110                 * a volume, retry looking for any volume. 
111                 */
112                if (InChanger) {
113                   InChanger = false;
114                   if (!ok) {
115                      continue;           /* retry again accepting any volume */
116                   }
117                }
118             }
119          }
120
121
122          if (!ok && create) {
123             /*
124              * 6. Try "creating" a new Volume
125              */
126             ok = newVolume(jcr, mr);
127          }
128          /*
129           *  Look at more drastic ways to find an Appendable Volume
130           */
131          if (!ok && (jcr->pool->purge_oldest_volume ||
132                      jcr->pool->recycle_oldest_volume)) {
133             Dmsg2(200, "No next volume found. PurgeOldest=%d\n RecyleOldest=%d",
134                 jcr->pool->purge_oldest_volume, jcr->pool->recycle_oldest_volume);
135             /* Find oldest volume to recycle */
136             ok = db_find_next_volume(jcr, jcr->db, -1, InChanger, mr);
137             Dmsg1(400, "Find oldest=%d\n", ok);
138             if (ok && prune) {
139                UAContext *ua;
140                Dmsg0(400, "Try purge.\n");
141                /*
142                 * 7.  Try to purging oldest volume only if not UA calling us.
143                 */
144                ua = new_ua_context(jcr);
145                if (jcr->pool->purge_oldest_volume && create) {
146                   Jmsg(jcr, M_INFO, 0, _("Purging oldest volume \"%s\"\n"), mr->VolumeName);
147                   ok = purge_jobs_from_volume(ua, mr);
148                /*
149                 * 8. or try recycling the oldest volume
150                 */
151                } else if (jcr->pool->recycle_oldest_volume) {
152                   Jmsg(jcr, M_INFO, 0, _("Pruning oldest volume \"%s\"\n"), mr->VolumeName);
153                   ok = prune_volume(ua, mr);
154                }
155                free_ua_context(ua);
156                if (ok) {
157                   ok = recycle_volume(jcr, mr);
158                   Dmsg1(400, "Recycle after purge oldest=%d\n", ok);
159                }
160             }
161          }
162       }
163       Dmsg2(100, "VolJobs=%d FirstWritten=%d\n", mr->VolJobs, mr->FirstWritten);
164       if (ok) {
165          /* If we can use the volume, check if it is expired */
166          if (has_volume_expired(jcr, mr)) {
167             if (retry++ < 200) {            /* sanity check */
168                continue;                    /* try again from the top */
169             } else {
170                Jmsg(jcr, M_ERROR, 0, _(
171 "We seem to be looping trying to find the next volume. I give up.\n"));
172             }
173          }
174       }
175       break;
176    } /* end for loop */
177    db_unlock(jcr->db);
178    Dmsg1(150, "return ok=%d find_next_vol\n", ok);
179    return ok;
180 }
181
182 /*
183  * Check if any time limits or use limits have expired
184  *   if so, set the VolStatus appropriately.
185  */
186 bool has_volume_expired(JCR *jcr, MEDIA_DBR *mr)
187 {
188    bool expired = false;
189    /*
190     * Check limits and expirations if "Append" and it has been used
191     * i.e. mr->VolJobs > 0
192     *
193     */
194    if (strcmp(mr->VolStatus, "Append") == 0 && mr->VolJobs > 0) {
195       /* First handle Max Volume Bytes */
196       if ((mr->MaxVolBytes > 0 && mr->VolBytes >= mr->MaxVolBytes)) {
197          Jmsg(jcr, M_INFO, 0, _("Max Volume bytes exceeded. "
198              "Marking Volume \"%s\" as Full.\n"), mr->VolumeName);
199          bstrncpy(mr->VolStatus, "Full", sizeof(mr->VolStatus));
200          expired = true;
201
202       /* Now see if Volume should only be used once */
203       } else if (mr->VolBytes > 0 && jcr->pool->use_volume_once) {
204          Jmsg(jcr, M_INFO, 0, _("Volume used once. "
205              "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
206          bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
207          expired = true;
208
209       /* Now see if Max Jobs written to volume */
210       } else if (mr->MaxVolJobs > 0 && mr->MaxVolJobs <= mr->VolJobs) {
211          Jmsg(jcr, M_INFO, 0, _("Max Volume jobs exceeded. "
212              "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
213          Dmsg3(100, "MaxVolJobs=%d JobId=%d Vol=%s\n", mr->MaxVolJobs,
214                (uint32_t)jcr->JobId, 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 bool get_scratch_volume(JCR *jcr, bool InChanger, MEDIA_DBR *mr)
332 {
333    MEDIA_DBR smr;                        /* for searching scratch pool */
334    POOL_DBR spr, pr;
335    bool ok = false;
336    bool found = false;
337
338    /* Only one thread at a time can pull from the scratch pool */
339    P(mutex);
340    /* 
341     * Get Pool record for Scratch Pool
342     */
343    memset(&spr, 0, sizeof(spr));
344    bstrncpy(spr.Name, "Scratch", sizeof(spr.Name));
345    if (db_get_pool_record(jcr, jcr->db, &spr)) {
346       memset(&smr, 0, sizeof(smr));
347       smr.PoolId = spr.PoolId;
348       if (InChanger) {       
349          smr.StorageId = mr->StorageId;  /* want only Scratch Volumes in changer */
350       }
351       bstrncpy(smr.VolStatus, "Append", sizeof(smr.VolStatus));  /* want only appendable volumes */
352       bstrncpy(smr.MediaType, mr->MediaType, sizeof(smr.MediaType));
353
354       /*
355        * If we do not find a valid Scratch volume, try
356        *  recycling any existing purged volumes, then
357        *  try to take the oldest volume.
358        */
359       if (db_find_next_volume(jcr, jcr->db, 1, InChanger, &smr)) {
360          found = true;
361
362       } else if (find_recycled_volume(jcr, InChanger, &smr)) {
363          found = true;
364
365       } else if (recycle_oldest_purged_volume(jcr, InChanger, &smr)) {
366          found = true;
367       }
368
369       if (found) {
370          POOL_MEM query(PM_MESSAGE);
371
372          /*   
373           * Get pool record where the Scratch Volume will go to ensure
374           * that we can add a Volume.
375           */
376          memset(&pr, 0, sizeof(pr));
377          bstrncpy(pr.Name, jcr->pool->name(), sizeof(pr.Name));
378
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          
385          /* Make sure there is room for another volume */
386          if (pr.MaxVols > 0 && pr.NumVols >= pr.MaxVols) {
387             Jmsg(jcr, M_WARNING, 0, _("Unable add Scratch Volume, Pool \"%s\" full MaxVols=%d\n"),
388                  jcr->pool->name(), pr.MaxVols);
389             goto bail_out;
390          }
391
392          memcpy(mr, &smr, sizeof(MEDIA_DBR)); 
393
394          /* Set default parameters from current pool */
395          set_pool_dbr_defaults_in_media_dbr(mr, &pr);
396
397          /*
398           * set_pool_dbr_defaults_in_media_dbr set VolStatus to Append,
399           *   we could have Recycled media, also, we retain the old
400           *   RecyclePoolId.
401           */
402          bstrncpy(mr->VolStatus, smr.VolStatus, sizeof(smr.VolStatus));
403          mr->RecyclePoolId = smr.RecyclePoolId;
404
405          if (!db_update_media_record(jcr, jcr->db, mr)) {
406             Jmsg(jcr, M_WARNING, 0, _("Failed to move Scratch Volume. ERR=%s\n"),
407                  db_strerror(jcr->db));
408             goto bail_out;
409          }
410
411          Jmsg(jcr, M_INFO, 0, _("Using Volume \"%s\" from 'Scratch' pool.\n"), 
412               mr->VolumeName);
413          
414          ok = true;
415       }
416    }
417 bail_out:
418    V(mutex);
419    return ok;
420 }