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