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