1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2002-2012 Free Software Foundation Europe e.V.
5 Copyright (C) 2011-2016 Planets Communications B.V.
6 Copyright (C) 2013-2019 Bareos GmbH & Co. KG
7
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version three of the GNU Affero General Public
10 License as published by the Free Software Foundation and included
11 in the file LICENSE.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Affero General Public License for more details.
17
18 You should have received a copy of the GNU Affero General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 02110-1301, USA.
22 */
23 /*
24 * Kern Sibbald, May MMII
25 */
26 /**
27 * @file
28 * Automatic Pruning Applies retention periods
29 */
30
31 #include "include/bareos.h"
32 #include "dird.h"
33 #include "dird/jcr_private.h"
34 #include "dird/next_vol.h"
35 #include "dird/ua_server.h"
36 #include "dird/ua_prune.h"
37 #include "dird/ua_purge.h"
38 #include "lib/edit.h"
39
40 namespace directordaemon {
41
42 /**
43 * Auto Prune Jobs and Files. This is called at the end of every
44 * Job. We do not prune volumes here.
45 */
DoAutoprune(JobControlRecord * jcr)46 void DoAutoprune(JobControlRecord* jcr)
47 {
48 UaContext* ua;
49 JobResource* job;
50 ClientResource* client;
51 PoolResource* pool;
52 bool pruned;
53
54 if (!jcr->impl->res.client) { /* temp -- remove me */
55 return;
56 }
57
58 ua = new_ua_context(jcr);
59 job = jcr->impl->res.job;
60 client = jcr->impl->res.client;
61 pool = jcr->impl->res.pool;
62
63 if (job->PruneJobs || client->AutoPrune) {
64 PruneJobs(ua, client, pool, jcr->getJobType());
65 pruned = true;
66 } else {
67 pruned = false;
68 }
69
70 if (job->PruneFiles || client->AutoPrune) {
71 PruneFiles(ua, client, pool);
72 pruned = true;
73 }
74 if (pruned) { Jmsg(jcr, M_INFO, 0, _("End auto prune.\n\n")); }
75 FreeUaContext(ua);
76 return;
77 }
78
79 /**
80 * Prune at least one Volume in current Pool. This is called from catreq.c =>
81 * next_vol.c when the Storage daemon is asking for another volume and no
82 * appendable volumes are available.
83 */
PruneVolumes(JobControlRecord * jcr,bool InChanger,MediaDbRecord * mr,StorageResource * store)84 void PruneVolumes(JobControlRecord* jcr,
85 bool InChanger,
86 MediaDbRecord* mr,
87 StorageResource* store)
88 {
89 int i;
90 int count;
91 UaContext* ua;
92 dbid_list ids;
93 del_ctx prune_list;
94 PoolMem query(PM_MESSAGE);
95 char ed1[50], ed2[100], ed3[50];
96
97 Dmsg1(100, "Prune volumes PoolId=%d\n", jcr->impl->jr.PoolId);
98 if (!jcr->impl->res.job->PruneVolumes && !jcr->impl->res.pool->AutoPrune) {
99 Dmsg0(100, "AutoPrune not set in Pool.\n");
100 return;
101 }
102
103 prune_list.max_ids = 10000;
104 prune_list.JobId = (JobId_t*)malloc(sizeof(JobId_t) * prune_list.max_ids);
105
106 ua = new_ua_context(jcr);
107 DbLock(jcr->db);
108
109 /*
110 * Edit PoolId
111 */
112 edit_int64(mr->PoolId, ed1);
113
114 /*
115 * Get Pool record for Scratch Pool
116 */
117 PoolDbRecord spr;
118 bstrncpy(spr.Name, "Scratch", sizeof(spr.Name));
119 if (jcr->db->GetPoolRecord(jcr, &spr)) {
120 edit_int64(spr.PoolId, ed2);
121 bstrncat(ed2, ",", sizeof(ed2));
122 } else {
123 ed2[0] = 0;
124 }
125
126 if (mr->ScratchPoolId) {
127 edit_int64(mr->ScratchPoolId, ed3);
128 bstrncat(ed2, ed3, sizeof(ed2));
129 bstrncat(ed2, ",", sizeof(ed2));
130 }
131
132 Dmsg1(100, "Scratch pool(s)=%s\n", ed2);
133 /*
134 * ed2 ends up with scratch poolid and current poolid or
135 * just current poolid if there is no scratch pool
136 */
137 bstrncat(ed2, ed1, sizeof(ed2));
138
139 /*
140 * Get the List of all media ids in the current Pool or whose
141 * RecyclePoolId is the current pool or the scratch pool
142 */
143 const char* select
144 = "SELECT DISTINCT MediaId,LastWritten FROM Media WHERE "
145 "(PoolId=%s OR RecyclePoolId IN (%s)) AND MediaType='%s' %s"
146 "ORDER BY LastWritten ASC,MediaId";
147
148 if (InChanger) {
149 char changer[100];
150 /* Ensure it is in this autochanger */
151 Bsnprintf(changer, sizeof(changer), "AND InChanger=1 AND StorageId=%s ",
152 edit_int64(mr->StorageId, ed3));
153 Mmsg(query, select, ed1, ed2, mr->MediaType, changer);
154 } else {
155 Mmsg(query, select, ed1, ed2, mr->MediaType, "");
156 }
157
158 Dmsg1(100, "query=%s\n", query.c_str());
159 if (!jcr->db->GetQueryDbids(ua->jcr, query, ids)) {
160 Jmsg(jcr, M_ERROR, 0, "%s", jcr->db->strerror());
161 goto bail_out;
162 }
163
164 Dmsg1(100, "Volume prune num_ids=%d\n", ids.num_ids);
165
166 /* Visit each Volume and Prune it until we find one that is purged */
167 for (i = 0; i < ids.num_ids; i++) {
168 MediaDbRecord lmr;
169
170 lmr.MediaId = ids.DBId[i];
171 Dmsg1(100, "Get record MediaId=%d\n", (int)lmr.MediaId);
172 if (!jcr->db->GetMediaRecord(jcr, &lmr)) {
173 Jmsg(jcr, M_ERROR, 0, "%s", jcr->db->strerror());
174 continue;
175 }
176 Dmsg1(100, "Examine vol=%s\n", lmr.VolumeName);
177 /* Don't prune archived volumes */
178 if (lmr.Enabled == VOL_ARCHIVED) {
179 Dmsg1(100, "Vol=%s disabled\n", lmr.VolumeName);
180 continue;
181 }
182 /* Prune only Volumes with status "Full", or "Used" */
183 if (bstrcmp(lmr.VolStatus, "Full") || bstrcmp(lmr.VolStatus, "Used")) {
184 Dmsg2(100, "Add prune list MediaId=%d Volume %s\n", (int)lmr.MediaId,
185 lmr.VolumeName);
186 count = GetPruneListForVolume(ua, &lmr, &prune_list);
187 Dmsg1(100, "Num pruned = %d\n", count);
188 if (count != 0) {
189 PurgeJobListFromCatalog(ua, prune_list);
190 prune_list.num_ids = 0; /* reset count */
191 }
192 if (!IsVolumePurged(ua, &lmr)) {
193 Dmsg1(050, "Vol=%s not pruned\n", lmr.VolumeName);
194 continue;
195 }
196 Dmsg1(050, "Vol=%s is purged\n", lmr.VolumeName);
197
198 /*
199 * Since we are also pruning the Scratch pool, continue until and check if
200 * this volume is available (InChanger + StorageId) If not, just skip this
201 * volume and try the next one
202 */
203 if (InChanger) {
204 if (!lmr.InChanger || (lmr.StorageId != mr->StorageId)) {
205 Dmsg1(100, "Vol=%s not inchanger or correct StoreId\n",
206 lmr.VolumeName);
207 continue; /* skip this volume, ie not loadable */
208 }
209 }
210 if (!lmr.Recycle) {
211 Dmsg1(100, "Vol=%s not recyclable\n", lmr.VolumeName);
212 continue;
213 }
214
215 if (HasVolumeExpired(jcr, &lmr)) {
216 Dmsg1(100, "Vol=%s has expired\n", lmr.VolumeName);
217 continue; /* Volume not usable */
218 }
219
220 /*
221 * If purged and not moved to another Pool, then we stop pruning and take
222 * this volume.
223 */
224 if (lmr.PoolId == mr->PoolId) {
225 Dmsg2(100, "Got Vol=%s MediaId=%d purged.\n", lmr.VolumeName,
226 (int)lmr.MediaId);
227 memcpy(mr, &lmr, sizeof(MediaDbRecord));
228 SetStorageidInMr(store, mr);
229 break; /* got a volume */
230 }
231 }
232 }
233
234 bail_out:
235 Dmsg0(100, "Leave prune volumes\n");
236 DbUnlock(jcr->db);
237 FreeUaContext(ua);
238 if (prune_list.JobId) { free(prune_list.JobId); }
239 return;
240 }
241 } /* namespace directordaemon */
242