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