]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_prune.c
kes Start work on new more efficient DBId subroutine. First use
[bacula/bacula] / bacula / src / dird / ua_prune.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2002-2007 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU 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    General Public License for more details.
17
18    You should have received a copy of the GNU 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    Bacula® is a registered trademark of John Walker.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *
30  *   Bacula Director -- User Agent Database prune Command
31  *      Applies retention periods
32  *
33  *     Kern Sibbald, February MMII
34  *
35  *   Version $Id$
36  */
37
38 #include "bacula.h"
39 #include "dird.h"
40
41 /* Imported functions */
42
43 /* Forward referenced functions */
44
45 /*
46  * Called here to count entries to be deleted
47  */
48 int del_count_handler(void *ctx, int num_fields, char **row)
49 {
50    struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
51
52    if (row[0]) {
53       cnt->count = str_to_int64(row[0]);
54    } else {
55       cnt->count = 0;
56    }
57    return 0;
58 }
59
60
61 /*
62  * Called here to make in memory list of JobIds to be
63  *  deleted and the associated PurgedFiles flag.
64  *  The in memory list will then be transversed
65  *  to issue the SQL DELETE commands.  Note, the list
66  *  is allowed to get to MAX_DEL_LIST_LEN to limit the
67  *  maximum malloc'ed memory.
68  */
69 int job_delete_handler(void *ctx, int num_fields, char **row)
70 {
71    struct del_ctx *del = (struct del_ctx *)ctx;
72
73    if (del->num_ids == MAX_DEL_LIST_LEN) {
74       return 1;
75    }
76    if (del->num_ids == del->max_ids) {
77       del->max_ids = (del->max_ids * 3) / 2;
78       del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
79       del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
80    }
81    del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
82 // Dmsg2(60, "row=%d val=%d\n", del->num_ids, del->JobId[del->num_ids]);
83    del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[1]);
84    return 0;
85 }
86
87 int file_delete_handler(void *ctx, int num_fields, char **row)
88 {
89    struct del_ctx *del = (struct del_ctx *)ctx;
90
91    if (del->num_ids == MAX_DEL_LIST_LEN) {
92       return 1;
93    }
94    if (del->num_ids == del->max_ids) {
95       del->max_ids = (del->max_ids * 3) / 2;
96       del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
97          del->max_ids);
98    }
99    del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
100 // Dmsg2(150, "row=%d val=%d\n", del->num_ids-1, del->JobId[del->num_ids-1]);
101    return 0;
102 }
103
104 /*
105  *   Prune records from database
106  *
107  *    prune files (from) client=xxx
108  *    prune jobs (from) client=xxx
109  *    prune volume=xxx
110  */
111 int prunecmd(UAContext *ua, const char *cmd)
112 {
113    CLIENT *client;
114    POOL_DBR pr;
115    MEDIA_DBR mr;
116    int kw;
117
118    static const char *keywords[] = {
119       NT_("Files"),
120       NT_("Jobs"),
121       NT_("Volume"),
122       NULL};
123
124    if (!open_client_db(ua)) {
125       return false;
126    }
127
128    /* First search args */
129    kw = find_arg_keyword(ua, keywords);
130    if (kw < 0 || kw > 2) {
131       /* no args, so ask user */
132       kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);
133    }
134
135    switch (kw) {
136    case 0:  /* prune files */
137       client = get_client_resource(ua);
138       if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
139          return false;
140       }
141       prune_files(ua, client);
142       return true;
143    case 1:  /* prune jobs */
144       client = get_client_resource(ua);
145       if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
146          return false;
147       }
148       /* ****FIXME**** allow user to select JobType */
149       prune_jobs(ua, client, JT_BACKUP);
150       return 1;
151    case 2:  /* prune volume */
152       if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
153          return false;
154       }
155       if (mr.Enabled == 2) {
156          ua->error_msg(_("Cannot prune Volume \"%s\" because it is archived.\n"),
157             mr.VolumeName);
158          return false;
159       }
160       if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
161          return false;
162       }
163       prune_volume(ua, &mr);
164       return true;
165    default:
166       break;
167    }
168
169    return true;
170 }
171
172 /*
173  * Prune File records from the database. For any Job which
174  * is older than the retention period, we unconditionally delete
175  * all File records for that Job.  This is simple enough that no
176  * temporary tables are needed. We simply make an in memory list of
177  * the JobIds meeting the prune conditions, then delete all File records
178  * pointing to each of those JobIds.
179  *
180  * This routine assumes you want the pruning to be done. All checking
181  *  must be done before calling this routine.
182  */
183 int prune_files(UAContext *ua, CLIENT *client)
184 {
185    struct del_ctx del;
186    struct s_count_ctx cnt;
187    POOL_MEM query(PM_MESSAGE);
188    utime_t now, period;
189    CLIENT_DBR cr;
190    char ed1[50], ed2[50];
191
192    db_lock(ua->db);
193    memset(&cr, 0, sizeof(cr));
194    memset(&del, 0, sizeof(del));
195    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
196    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
197       db_unlock(ua->db);
198       return 0;
199    }
200
201    period = client->FileRetention;
202    now = (utime_t)time(NULL);
203
204    /* Select Jobs -- for counting */
205    Mmsg(query, count_select_job, edit_uint64(now - period, ed1), 
206         edit_int64(cr.ClientId, ed2));
207    Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, 
208                (uint32_t)period, query.c_str());
209    cnt.count = 0;
210    if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
211       ua->error_msg("%s", db_strerror(ua->db));
212       Dmsg0(050, "Count failed\n");
213       goto bail_out;
214    }
215
216    if (cnt.count == 0) {
217       if (ua->verbose) {
218          ua->warning_msg(_("No Files found to prune.\n"));
219       }
220       goto bail_out;
221    }
222
223    if (cnt.count < MAX_DEL_LIST_LEN) {
224       del.max_ids = cnt.count + 1;
225    } else {
226       del.max_ids = MAX_DEL_LIST_LEN;
227    }
228    del.tot_ids = 0;
229
230    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
231
232    /* Now process same set but making a delete list */
233    Mmsg(query, select_job, edit_uint64(now - period, ed1), 
234         edit_int64(cr.ClientId, ed2));
235    db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del);
236
237    purge_files_from_job_list(ua, del);
238
239    edit_uint64_with_commas(del.num_del, ed1);
240    ua->info_msg(_("Pruned Files from %s Jobs for client %s from catalog.\n"),
241       ed1, client->name());
242
243 bail_out:
244    db_unlock(ua->db);
245    if (del.JobId) {
246       free(del.JobId);
247    }
248    return 1;
249 }
250
251
252 static void drop_temp_tables(UAContext *ua)
253 {
254    int i;
255    for (i=0; drop_deltabs[i]; i++) {
256       db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
257    }
258 }
259
260 static bool create_temp_tables(UAContext *ua)
261 {
262    int i;
263    /* Create temp tables and indicies */
264    for (i=0; create_deltabs[i]; i++) {
265       if (!db_sql_query(ua->db, create_deltabs[i], NULL, (void *)NULL)) {
266          ua->error_msg("%s", db_strerror(ua->db));
267          Dmsg0(050, "create DelTables table failed\n");
268          return false;
269       }
270    }
271    return true;
272 }
273
274
275
276 /*
277  * Pruning Jobs is a bit more complicated than purging Files
278  * because we delete Job records only if there is a more current
279  * backup of the FileSet. Otherwise, we keep the Job record.
280  * In other words, we never delete the only Job record that
281  * contains a current backup of a FileSet. This prevents the
282  * Volume from being recycled and destroying a current backup.
283  *
284  * For Verify Jobs, we do not delete the last InitCatalog.
285  *
286  * For Restore Jobs there are no restrictions.
287  */
288 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
289 {
290    struct del_ctx del;
291    POOL_MEM query(PM_MESSAGE);
292    utime_t now, period;
293    CLIENT_DBR cr;
294    char ed1[50], ed2[50];
295
296    db_lock(ua->db);
297    memset(&cr, 0, sizeof(cr));
298    memset(&del, 0, sizeof(del));
299
300    bstrncpy(cr.Name, client->name(), sizeof(cr.Name));
301    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
302       db_unlock(ua->db);
303       return 0;
304    }
305
306    period = client->JobRetention;
307    now = (utime_t)time(NULL);
308
309    /* Drop any previous temporary tables still there */
310    drop_temp_tables(ua);
311
312    /* Create temp tables and indicies */
313    if (!create_temp_tables(ua)) {
314       goto bail_out;
315    }
316
317    /*
318     * Select all files that are older than the JobRetention period
319     *  and stuff them into the "DeletionCandidates" table.
320     */
321    edit_uint64(now - period, ed1);
322    Mmsg(query, insert_delcand, (char)JobType, ed1, 
323         edit_int64(cr.ClientId, ed2));
324    if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) {
325       if (ua->verbose) {
326          ua->error_msg("%s", db_strerror(ua->db));
327       }
328       Dmsg0(050, "insert delcand failed\n");
329       goto bail_out;
330    }
331
332    del.max_ids = 100;
333    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
334    del.PurgedFiles = (char *)malloc(del.max_ids);
335
336    /* ed1 = JobTDate */
337    edit_int64(cr.ClientId, ed2);
338    switch (JobType) {
339    case JT_BACKUP:
340       Mmsg(query, select_backup_del, ed1, ed2);
341       break;
342    case JT_RESTORE:
343       Mmsg(query, select_restore_del, ed1, ed2);
344       break;
345    case JT_VERIFY:
346       Mmsg(query, select_verify_del, ed1, ed2);
347       break;
348    case JT_ADMIN:
349       Mmsg(query, select_admin_del, ed1, ed2);
350       break;
351    case JT_MIGRATE:
352       Mmsg(query, select_migrate_del, ed1, ed2);
353       break;
354    }
355
356    Dmsg1(150, "Query=%s\n", query.c_str());
357    if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
358       ua->error_msg("%s", db_strerror(ua->db));
359    }
360
361    purge_job_list_from_catalog(ua, del);
362
363    if (del.num_del > 0) {
364       ua->info_msg(_("Pruned %d %s for client %s from catalog.\n"), del.num_del,
365          del.num_del==1?_("Job"):_("Jobs"), client->name());
366     } else if (ua->verbose) {
367        ua->info_msg(_("No Jobs found to prune.\n"));
368     }
369
370 bail_out:
371    drop_temp_tables(ua);
372    db_unlock(ua->db);
373    if (del.JobId) {
374       free(del.JobId);
375    }
376    if (del.PurgedFiles) {
377       free(del.PurgedFiles);
378    }
379    return 1;
380 }
381
382 /*
383  * Prune a given Volume
384  */
385 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
386 {
387    POOL_MEM query(PM_MESSAGE);
388    struct del_ctx del;
389    bool ok = false;
390    int count;
391
392    if (mr->Enabled == 2) {
393       return false;                   /* Cannot prune archived volumes */
394    }
395
396    memset(&del, 0, sizeof(del));
397    del.max_ids = 10000;
398    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
399
400    db_lock(ua->db);
401
402    /* Prune only Volumes with status "Full", or "Used" */
403    if (strcmp(mr->VolStatus, "Full")   == 0 ||
404        strcmp(mr->VolStatus, "Used")   == 0) {
405       Dmsg2(050, "get prune list MediaId=%d Volume %s\n", (int)mr->MediaId, mr->VolumeName);
406       count = get_prune_list_for_volume(ua, mr, &del);
407       Dmsg1(050, "Num pruned = %d\n", count);
408       if (count != 0) {
409          purge_job_list_from_catalog(ua, del);
410       }
411       ok = is_volume_purged(ua, mr);
412    }
413
414    db_unlock(ua->db);
415    if (del.JobId) {
416       free(del.JobId);
417    }
418    return ok;
419 }
420
421 /*
422  * Get prune list for a volume
423  */
424 int get_prune_list_for_volume(UAContext *ua, MEDIA_DBR *mr, del_ctx *del)
425 {
426    POOL_MEM query(PM_MESSAGE);
427    int count = 0;
428    int i;          
429    utime_t now, period;
430    char ed1[50], ed2[50];
431
432    if (mr->Enabled == 2) {
433       return 0;                    /* cannot prune Archived volumes */
434    }
435
436    db_lock(ua->db);
437
438    /*
439     * Now add to the  list of JobIds for Jobs written to this Volume
440     */
441    edit_int64(mr->MediaId, ed1); 
442    period = mr->VolRetention;
443    now = (utime_t)time(NULL);
444    edit_uint64(now-period, ed2);
445    Mmsg(query, sel_JobMedia, ed1, ed2);
446    Dmsg3(250, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
447       (int)(now-period));
448
449    Dmsg1(050, "Query=%s\n", query.c_str());
450    if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)del)) {
451       if (ua->verbose) {
452          ua->error_msg("%s", db_strerror(ua->db));
453       }
454       Dmsg0(050, "Count failed\n");
455       goto bail_out;
456    }
457
458    for (i=0; i < del->num_ids; i++) {
459       if (ua->jcr->JobId == del->JobId[i]) {
460          Dmsg2(150, "skip same job JobId[%d]=%d\n", i, (int)del->JobId[i]);
461          del->JobId[i] = 0;
462          continue;
463       }
464       Dmsg2(150, "accept JobId[%d]=%d\n", i, (int)del->JobId[i]);
465       count++;
466    }
467
468 bail_out:
469    db_unlock(ua->db);
470    return count;
471 }