2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2017 Kern Sibbald
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.
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.
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
20 * Bacula Director -- Automatic Pruning
21 * Applies retention periods
23 * Kern Sibbald, May MMII
30 /* Forward referenced functions */
34 * Auto Prune Jobs and Files. This is called at the end of every
35 * Job. We do not prune volumes here.
37 void do_autoprune(JCR *jcr)
44 if (!jcr->client) { /* temp -- remove me */
48 ua = new_ua_context(jcr);
52 if (jcr->job->PruneJobs || jcr->client->AutoPrune) {
53 prune_jobs(ua, client, pool, jcr->getJobType());
59 if (jcr->job->PruneFiles || jcr->client->AutoPrune) {
60 prune_files(ua, client, pool);
64 Jmsg(jcr, M_INFO, 0, _("End auto prune.\n\n"));
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.
76 void prune_volumes(JCR *jcr, bool InChanger, MEDIA_DBR *mr,
82 struct del_ctx prune_list;
83 POOL_MEM query(PM_MESSAGE), changer(PM_MESSAGE);
85 char ed1[50], ed2[100], ed3[50];
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");
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);
99 ua = new_ua_context(jcr);
103 edit_int64(mr->PoolId, ed1);
105 * Get Pool record for Scratch Pool
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));
116 if (mr->ScratchPoolId) {
117 edit_int64(mr->ScratchPoolId, ed3);
118 bstrncat(ed2, ed3, sizeof(ed2));
119 bstrncat(ed2, ",", sizeof(ed2));
122 Dmsg1(100, "Scratch pool(s)=%s\n", ed2);
124 * ed2 ends up with scratch poolid and current poolid or
125 * just current poolid if there is no scratch pool
127 bstrncat(ed2, ed1, sizeof(ed2));
130 * Get the List of all media ids in the current Pool or whose
131 * RecyclePoolId is the current pool or the scratch pool
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";
137 set_storageid_in_mr(store, mr);
139 Mmsg(changer, "AND InChanger=1 AND StorageId IN (%s) ", mr->sid_group);
142 Mmsg(query, select, ed1, ed2, mr->MediaType, changer.c_str());
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));
150 Dmsg1(100, "Volume prune num_ids=%d\n", ids.num_ids);
152 /* Visit each Volume and Prune it until we find one that is purged */
153 for (i=0; i<ids.num_ids; i++) {
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));
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);
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);
174 purge_job_list_from_catalog(ua, prune_list);
175 prune_list.num_ids = 0; /* reset count */
177 if (!is_volume_purged(ua, &lmr)) {
178 Dmsg1(100, "Vol=%s not pruned\n", lmr.VolumeName);
181 Dmsg1(100, "Vol=%s is purged\n", lmr.VolumeName);
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
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 */
197 Dmsg1(100, "Vol=%s not recyclable\n", lmr.VolumeName);
201 if (has_volume_expired(jcr, &lmr)) {
202 Dmsg1(100, "Vol=%s has expired\n", lmr.VolumeName);
203 continue; /* Volume not usable */
207 * If purged and not moved to another Pool,
208 * then we stop pruning and take this volume.
210 if (lmr.PoolId == mr->PoolId) {
211 Dmsg2(100, "Got Vol=%s MediaId=%lu purged.\n", lmr.VolumeName, lmr.MediaId);
213 set_storageid_in_mr(store, mr);
214 break; /* got a volume */
220 Dmsg0(100, "Leave prune volumes\n");
223 if (prune_list.JobId) {
224 free(prune_list.JobId);