]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/autoprune.c
Fix bug #2343 where truncate of explicit Volume name truncates non-purged volumes
[bacula/bacula] / bacula / src / dird / autoprune.c
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2017 Kern Sibbald
5
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13
14    This notice must be preserved when any source code is 
15    conveyed and/or propagated.
16
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20  *   Bacula Director -- Automatic Pruning
21  *      Applies retention periods
22  *
23  *     Kern Sibbald, May MMII
24  */
25
26 #include "bacula.h"
27 #include "dird.h"
28 #include "ua.h"
29
30 /* Forward referenced functions */
31
32
33 /*
34  * Auto Prune Jobs and Files. This is called at the end of every
35  *   Job.  We do not prune volumes here.
36  */
37 void do_autoprune(JCR *jcr)
38 {
39    UAContext *ua;
40    CLIENT *client;
41    POOL *pool;
42    bool pruned;
43
44    if (!jcr->client) {                /* temp -- remove me */
45       return;
46    }
47
48    ua = new_ua_context(jcr);
49    client = jcr->client;
50    pool = jcr->pool;
51
52    if (jcr->job->PruneJobs || jcr->client->AutoPrune) {
53       prune_jobs(ua, client, pool, jcr->getJobType());
54       pruned = true;
55    } else {
56       pruned = false;
57    }
58
59    if (jcr->job->PruneFiles || jcr->client->AutoPrune) {
60       prune_files(ua, client, pool);
61       pruned = true;
62    }
63    if (pruned) {
64       Jmsg(jcr, M_INFO, 0, _("End auto prune.\n\n"));
65    }
66    free_ua_context(ua);
67    return;
68 }
69
70 /*
71  * Prune at least one Volume in current Pool. This is called from
72  *   catreq.c => next_vol.c when the Storage daemon is asking for another
73  *   volume and no appendable volumes are available.
74  *
75  */
76 void prune_volumes(JCR *jcr, bool InChanger, MEDIA_DBR *mr,
77         STORE *store)
78 {
79    int count;
80    int i;
81    dbid_list ids;
82    struct del_ctx prune_list;
83    POOL_MEM query(PM_MESSAGE), changer(PM_MESSAGE);
84    UAContext *ua;
85    char ed1[50], ed2[100], ed3[50];
86
87    POOL_DBR spr;
88
89    Dmsg1(100, "Prune volumes PoolId=%d\n", jcr->jr.PoolId);
90    if (!jcr->job->PruneVolumes && !jcr->pool->AutoPrune) {
91       Dmsg0(100, "AutoPrune not set in Pool.\n");
92       return;
93    }
94
95    memset(&prune_list, 0, sizeof(prune_list));
96    prune_list.max_ids = 10000;
97    prune_list.JobId = (JobId_t *)malloc(sizeof(JobId_t) * prune_list.max_ids);
98
99    ua = new_ua_context(jcr);
100    db_lock(jcr->db);
101
102    /* Edit PoolId */
103    edit_int64(mr->PoolId, ed1);
104    /*
105     * Get Pool record for Scratch Pool
106     */
107    memset(&spr, 0, sizeof(spr));
108    bstrncpy(spr.Name, "Scratch", sizeof(spr.Name));
109    if (db_get_pool_record(jcr, jcr->db, &spr)) {
110       edit_int64(spr.PoolId, ed2);
111       bstrncat(ed2, ",", sizeof(ed2));
112    } else {
113       ed2[0] = 0;
114    }
115
116    if (mr->ScratchPoolId) {
117       edit_int64(mr->ScratchPoolId, ed3);
118       bstrncat(ed2, ed3, sizeof(ed2));
119       bstrncat(ed2, ",", sizeof(ed2));
120    }
121
122    Dmsg1(100, "Scratch pool(s)=%s\n", ed2);
123    /*
124     * ed2 ends up with scratch poolid and current poolid or
125     *   just current poolid if there is no scratch pool
126     */
127    bstrncat(ed2, ed1, sizeof(ed2));
128
129    /*
130     * Get the List of all media ids in the current Pool or whose
131     *  RecyclePoolId is the current pool or the scratch pool
132     */
133    const char *select = "SELECT DISTINCT MediaId,LastWritten FROM Media WHERE "
134         "(PoolId=%s OR RecyclePoolId IN (%s)) AND MediaType='%s' %s"
135         "ORDER BY LastWritten ASC,MediaId";
136
137    set_storageid_in_mr(store, mr);
138    if (InChanger) {
139       Mmsg(changer, "AND InChanger=1 AND StorageId IN (%s) ", mr->sid_group);
140    }
141
142    Mmsg(query, select, ed1, ed2, mr->MediaType, changer.c_str());
143
144    Dmsg1(100, "query=%s\n", query.c_str());
145    if (!db_get_query_dbids(ua->jcr, ua->db, query, ids)) {
146       Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
147       goto bail_out;
148    }
149
150    Dmsg1(100, "Volume prune num_ids=%d\n", ids.num_ids);
151
152    /* Visit each Volume and Prune it until we find one that is purged */
153    for (i=0; i<ids.num_ids; i++) {
154       MEDIA_DBR lmr;
155       lmr.MediaId = ids.DBId[i];
156       Dmsg1(100, "Get record MediaId=%lu\n", lmr.MediaId);
157       if (!db_get_media_record(jcr, jcr->db, &lmr)) {
158          Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
159          continue;
160       }
161       Dmsg1(100, "Examine vol=%s\n", lmr.VolumeName);
162       /* Don't prune archived volumes */
163       if (lmr.Enabled == 2) {
164          Dmsg1(100, "Vol=%s disabled\n", lmr.VolumeName);
165          continue;
166       }
167       /* Prune only Volumes with status "Full", or "Used" */
168       if (strcmp(lmr.VolStatus, "Full")   == 0 ||
169           strcmp(lmr.VolStatus, "Used")   == 0) {
170          Dmsg2(100, "Add prune list MediaId=%lu Volume %s\n", lmr.MediaId, lmr.VolumeName);
171          count = get_prune_list_for_volume(ua, &lmr, &prune_list);
172          Dmsg1(100, "Num pruned = %d\n", count);
173          if (count != 0) {
174             purge_job_list_from_catalog(ua, prune_list);
175             prune_list.num_ids = 0;             /* reset count */
176          }
177          if (!is_volume_purged(ua, &lmr)) {
178             Dmsg1(100, "Vol=%s not pruned\n", lmr.VolumeName);
179             continue;
180          }
181          Dmsg1(100, "Vol=%s is purged\n", lmr.VolumeName);
182
183          /*
184           * Since we are also pruning the Scratch pool, continue
185           *   until and check if this volume is available (InChanger + StorageId)
186           * If not, just skip this volume and try the next one
187           */
188          if (InChanger) {
189             /* ***FIXME*** should be any StorageId in sid_group */
190             if (!lmr.InChanger || (lmr.StorageId != mr->StorageId)) {
191                Dmsg1(100, "Vol=%s not inchanger\n", lmr.VolumeName);
192                continue;                  /* skip this volume, ie not loadable */
193             }
194          }
195
196          if (!lmr.Recycle) {
197             Dmsg1(100, "Vol=%s not recyclable\n", lmr.VolumeName);
198             continue;
199          }
200
201          if (has_volume_expired(jcr, &lmr)) {
202             Dmsg1(100, "Vol=%s has expired\n", lmr.VolumeName);
203             continue;                     /* Volume not usable */
204          }
205
206          /*
207           * If purged and not moved to another Pool,
208           *   then we stop pruning and take this volume.
209           */
210          if (lmr.PoolId == mr->PoolId) {
211             Dmsg2(100, "Got Vol=%s MediaId=%lu purged.\n", lmr.VolumeName, lmr.MediaId);
212             mr->copy(&lmr);
213             set_storageid_in_mr(store, mr);
214             break;                        /* got a volume */
215          }
216       }
217    }
218
219 bail_out:
220    Dmsg0(100, "Leave prune volumes\n");
221    db_unlock(jcr->db);
222    free_ua_context(ua);
223    if (prune_list.JobId) {
224       free(prune_list.JobId);
225    }
226    return;
227 }