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