]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/next_vol.c
- Add Rudolf Cejka's new rc-chio-changer.
[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        * 2. Try pulling a Scratch volume if one exists in the autochanger
68        */
69       if (!ok && InChanger) {
70          ok = get_scratch_volume(jcr, mr, InChanger);
71       }
72
73       if (!ok) {
74          /*
75           * 3. Try finding a recycled volume
76           */
77          ok = find_recycled_volume(jcr, InChanger, mr);
78          Dmsg2(100, "find_recycled_volume %d FW=%d\n", ok, mr->FirstWritten);
79          if (!ok) {
80             /*
81              * 4. Try recycling any purged volume
82              */
83             ok = recycle_oldest_purged_volume(jcr, InChanger, mr);
84             if (!ok) {
85                /*
86                 * 5. Try pruning Volumes
87                 */
88                prune_volumes(jcr);
89                ok = recycle_oldest_purged_volume(jcr, InChanger, mr);
90                if (InChanger) {
91                   InChanger = false;
92                   if (!ok) {
93                      continue;           /* retry again accepting any volume */
94                   }
95                }
96             }
97          }
98
99          if (!ok) {
100             /*
101              * 6. Try pulling a volume from the Scratch pool
102              */ 
103             ok = get_scratch_volume(jcr, mr, InChanger);
104          }
105
106          if (!ok && create) {
107             /*
108              * 7. Try "creating" a new Volume
109              */
110             ok = newVolume(jcr, mr);
111          }
112          /*
113           *  Look at more drastic ways to find an Appendable Volume
114           */
115          if (!ok && (jcr->pool->purge_oldest_volume ||
116                      jcr->pool->recycle_oldest_volume)) {
117             Dmsg2(200, "No next volume found. PurgeOldest=%d\n RecyleOldest=%d",
118                 jcr->pool->purge_oldest_volume, jcr->pool->recycle_oldest_volume);
119             /* Find oldest volume to recycle */
120             ok = db_find_next_volume(jcr, jcr->db, -1, InChanger, mr);
121             Dmsg1(400, "Find oldest=%d\n", ok);
122             if (ok) {
123                UAContext *ua;
124                Dmsg0(400, "Try purge.\n");
125                /*
126                 * 8.  Try to purging oldest volume only if not UA calling us.
127                 */
128                ua = new_ua_context(jcr);
129                if (jcr->pool->purge_oldest_volume && create) {
130                   Jmsg(jcr, M_INFO, 0, _("Purging oldest volume \"%s\"\n"), mr->VolumeName);
131                   ok = purge_jobs_from_volume(ua, mr);
132                /*
133                 * 9. or try recycling the oldest volume
134                 */
135                } else if (jcr->pool->recycle_oldest_volume) {
136                   Jmsg(jcr, M_INFO, 0, _("Pruning oldest volume \"%s\"\n"), mr->VolumeName);
137                   ok = prune_volume(ua, mr);
138                }
139                free_ua_context(ua);
140                if (ok) {
141                   ok = recycle_volume(jcr, mr);
142                   Dmsg1(400, "Recycle after purge oldest=%d\n", ok);
143                }
144             }
145          }
146       }
147       Dmsg2(100, "VolJobs=%d FirstWritten=%d\n", mr->VolJobs, mr->FirstWritten);
148       if (ok) {
149          /* If we can use the volume, check if it is expired */
150          if (has_volume_expired(jcr, mr)) {
151             if (retry++ < 200) {            /* sanity check */
152                continue;                    /* try again from the top */
153             } else {
154                Jmsg(jcr, M_ERROR, 0, _(
155 "We seem to be looping trying to find the next volume. I give up.\n"));
156             }
157          }
158       }
159       break;
160    } /* end for loop */
161    db_unlock(jcr->db);
162    return ok;
163 }
164
165 /*
166  * Check if any time limits or use limits have expired
167  *   if so, set the VolStatus appropriately.
168  */
169 bool has_volume_expired(JCR *jcr, MEDIA_DBR *mr)
170 {
171    bool expired = false;
172    /*
173     * Check limits and expirations if "Append" and it has been used
174     * i.e. mr->VolJobs > 0
175     *
176     */
177    if (strcmp(mr->VolStatus, "Append") == 0 && mr->VolJobs > 0) {
178       /* First handle Max Volume Bytes */
179       if ((mr->MaxVolBytes > 0 && mr->VolBytes >= mr->MaxVolBytes)) {
180          Jmsg(jcr, M_INFO, 0, _("Max Volume bytes exceeded. "
181              "Marking Volume \"%s\" as Full.\n"), mr->VolumeName);
182          bstrncpy(mr->VolStatus, "Full", sizeof(mr->VolStatus));
183          expired = true;
184
185       /* Now see if Volume should only be used once */
186       } else if (mr->VolBytes > 0 && jcr->pool->use_volume_once) {
187          Jmsg(jcr, M_INFO, 0, _("Volume used once. "
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 Jobs written to volume */
193       } else if (mr->MaxVolJobs > 0 && mr->MaxVolJobs <= mr->VolJobs) {
194          Jmsg(jcr, M_INFO, 0, _("Max Volume jobs exceeded. "
195              "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
196          bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
197          expired = true;
198
199       /* Now see if Max Files written to volume */
200       } else if (mr->MaxVolFiles > 0 && mr->MaxVolFiles <= mr->VolFiles) {
201          Jmsg(jcr, M_INFO, 0, _("Max Volume files exceeded. "
202              "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
203          bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
204          expired = true;
205
206       /* Finally, check Use duration expiration */
207       } else if (mr->VolUseDuration > 0) {
208          utime_t now = time(NULL);
209          /* See if Vol Use has expired */
210          if (mr->VolUseDuration <= (now - mr->FirstWritten)) {
211             Jmsg(jcr, M_INFO, 0, _("Max configured use duration exceeded. "
212                "Marking Volume \"%s\" as Used.\n"), mr->VolumeName);
213             bstrncpy(mr->VolStatus, "Used", sizeof(mr->VolStatus));
214             expired = true;
215          }
216       }
217    }
218    if (expired) {
219       /* Need to update media */
220       if (!db_update_media_record(jcr, jcr->db, mr)) {
221          Jmsg(jcr, M_ERROR, 0, _("Catalog error updating volume \"%s\". ERR=%s"),
222               mr->VolumeName, db_strerror(jcr->db));
223       }
224    }
225    return expired;
226 }
227
228 /*
229  * Try hard to recycle the current volume
230  *
231  *  Returns: on failure - reason = NULL
232  *           on success - reason - pointer to reason
233  */
234 void check_if_volume_valid_or_recyclable(JCR *jcr, MEDIA_DBR *mr, const char **reason)
235 {
236    int ok;
237
238    *reason = NULL;
239
240    /*  Check if a duration or limit has expired */
241    if (has_volume_expired(jcr, mr)) {
242       *reason = _("volume has expired");
243       /* Keep going because we may be able to recycle volume */
244    }
245
246    /*
247     * Now see if we can use the volume as is
248     */
249    if (strcmp(mr->VolStatus, "Append") == 0 ||
250        strcmp(mr->VolStatus, "Recycle") == 0) {
251       *reason = NULL;
252       return;
253    }
254
255    /*
256     * Check if the Volume is already marked for recycling
257     */
258    if (strcmp(mr->VolStatus, "Purged") == 0) {
259       if (recycle_volume(jcr, mr)) {
260          Jmsg(jcr, M_INFO, 0, _("Recycled current volume \"%s\"\n"), mr->VolumeName);
261          *reason = NULL;
262          return;
263       } else {
264          /* In principle this shouldn't happen */
265          *reason = _("and recycling of current volume failed");
266          return;
267       }
268    }
269
270    /* At this point, the volume is not valid for writing */
271    *reason = _("but should be Append, Purged or Recycle");
272
273    /*
274     * What we're trying to do here is see if the current volume is
275     * "recyclable" - ie. if we prune all expired jobs off it, is
276     * it now possible to reuse it for the job that it is currently
277     * needed for?
278     */
279    if ((mr->LastWritten + mr->VolRetention) < (utime_t)time(NULL)
280          && mr->Recycle && jcr->pool->recycle_current_volume
281          && (strcmp(mr->VolStatus, "Full") == 0 ||
282             strcmp(mr->VolStatus, "Used") == 0)) {
283       /*
284        * Attempt prune of current volume to see if we can
285        * recycle it for use.
286        */
287       UAContext *ua;
288
289       ua = new_ua_context(jcr);
290       ok = prune_volume(ua, mr);
291       free_ua_context(ua);
292
293       if (ok) {
294          /* If fully purged, recycle current volume */
295          if (recycle_volume(jcr, mr)) {
296             Jmsg(jcr, M_INFO, 0, _("Recycled current volume \"%s\"\n"), mr->VolumeName);
297             *reason = NULL;
298          } else {
299             *reason = _("but should be Append, Purged or Recycle (recycling of the "
300                "current volume failed)");
301          }
302       } else {
303          *reason = _("but should be Append, Purged or Recycle (cannot automatically "
304             "recycle current volume, as it still contains unpruned data)");
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 }