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