3 * Bacula Director -- User Agent Database prune Command
4 * Applies retention periods
6 * Kern Sibbald, February MMII
11 Copyright (C) 2002-2006 Kern Sibbald
13 This program is free software; you can redistribute it and/or
14 modify it under the terms of the GNU General Public License
15 version 2 as amended with additional clauses defined in the
16 file LICENSE in the main source directory.
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 the file LICENSE for additional details.
28 /* Imported functions */
30 /* Forward referenced functions */
33 #define MAX_DEL_LIST_LEN 2000000
35 /* Imported variables */
36 extern char *select_job;
37 extern char *drop_deltabs[];
38 extern char *create_deltabs[];
39 extern char *insert_delcand;
40 extern char *select_backup_del;
41 extern char *select_verify_del;
42 extern char *select_restore_del;
43 extern char *select_admin_del;
44 extern char *cnt_File;
45 extern char *del_File;
46 extern char *upd_Purged;
47 extern char *cnt_DelCand;
50 extern char *del_JobMedia;
51 extern char *cnt_JobMedia;
52 extern char *sel_JobMedia;
55 /* In memory list of JobIds */
56 struct s_file_del_ctx {
58 int num_ids; /* ids stored */
59 int max_ids; /* size of array */
60 int num_del; /* number deleted */
61 int tot_ids; /* total to process */
64 struct s_job_del_ctx {
65 JobId_t *JobId; /* array of JobIds */
66 char *PurgedFiles; /* Array of PurgedFile flags */
67 int num_ids; /* ids stored */
68 int max_ids; /* size of array */
69 int num_del; /* number deleted */
70 int tot_ids; /* total to process */
79 * Called here to count entries to be deleted
81 static int count_handler(void *ctx, int num_fields, char **row)
83 struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
86 cnt->count = str_to_int64(row[0]);
95 * Called here to count the number of Jobs to be pruned
97 static int file_count_handler(void *ctx, int num_fields, char **row)
99 struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
106 * Called here to make in memory list of JobIds to be
107 * deleted and the associated PurgedFiles flag.
108 * The in memory list will then be transversed
109 * to issue the SQL DELETE commands. Note, the list
110 * is allowed to get to MAX_DEL_LIST_LEN to limit the
111 * maximum malloc'ed memory.
113 static int job_delete_handler(void *ctx, int num_fields, char **row)
115 struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
117 if (del->num_ids == MAX_DEL_LIST_LEN) {
120 if (del->num_ids == del->max_ids) {
121 del->max_ids = (del->max_ids * 3) / 2;
122 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
123 del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
125 del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
126 del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[1]);
130 static int file_delete_handler(void *ctx, int num_fields, char **row)
132 struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
134 if (del->num_ids == MAX_DEL_LIST_LEN) {
137 if (del->num_ids == del->max_ids) {
138 del->max_ids = (del->max_ids * 3) / 2;
139 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
142 del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
147 * Prune records from database
149 * prune files (from) client=xxx
150 * prune jobs (from) client=xxx
153 int prunecmd(UAContext *ua, const char *cmd)
160 static const char *keywords[] = {
170 /* First search args */
171 kw = find_arg_keyword(ua, keywords);
172 if (kw < 0 || kw > 2) {
173 /* no args, so ask user */
174 kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);
178 case 0: /* prune files */
179 client = get_client_resource(ua);
180 if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
183 prune_files(ua, client);
185 case 1: /* prune jobs */
186 client = get_client_resource(ua);
187 if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
190 /* ****FIXME**** allow user to select JobType */
191 prune_jobs(ua, client, JT_BACKUP);
193 case 2: /* prune volume */
194 if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
197 if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
200 prune_volume(ua, &mr);
210 * Prune File records from the database. For any Job which
211 * is older than the retention period, we unconditionally delete
212 * all File records for that Job. This is simple enough that no
213 * temporary tables are needed. We simply make an in memory list of
214 * the JobIds meeting the prune conditions, then delete all File records
215 * pointing to each of those JobIds.
217 * This routine assumes you want the pruning to be done. All checking
218 * must be done before calling this routine.
220 int prune_files(UAContext *ua, CLIENT *client)
222 struct s_file_del_ctx del;
223 POOLMEM *query = get_pool_memory(PM_MESSAGE);
227 char ed1[50], ed2[50];
230 memset(&cr, 0, sizeof(cr));
231 memset(&del, 0, sizeof(del));
232 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
233 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
238 period = client->FileRetention;
239 now = (utime_t)time(NULL);
241 /* Select Jobs -- for counting */
242 Mmsg(query, select_job, edit_uint64(now - period, ed1),
243 edit_int64(cr.ClientId, ed2));
244 Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, (uint32_t)period, query);
245 if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
247 bsendmsg(ua, "%s", db_strerror(ua->db));
249 Dmsg0(050, "Count failed\n");
253 if (del.tot_ids == 0) {
255 bsendmsg(ua, _("No Files found to prune.\n"));
260 if (del.tot_ids < MAX_DEL_LIST_LEN) {
261 del.max_ids = del.tot_ids + 1;
263 del.max_ids = MAX_DEL_LIST_LEN;
267 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
269 /* Now process same set but making a delete list */
270 db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
272 for (i=0; i < del.num_ids; i++) {
273 Mmsg(query, del_File, edit_int64(del.JobId[i], ed1));
274 Dmsg1(200, "Delete Files JobId=%s\n", ed1);
275 db_sql_query(ua->db, query, NULL, (void *)NULL);
277 * Now mark Job as having files purged. This is necessary to
278 * avoid having too many Jobs to process in future prunings. If
279 * we don't do this, the number of JobId's in our in memory list
280 * could grow very large.
282 Mmsg(query, upd_Purged, edit_int64(del.JobId[i], ed1));
283 db_sql_query(ua->db, query, NULL, (void *)NULL);
284 Dmsg1(200, "Update Purged sql=%s\n", query);
286 edit_uint64_with_commas(del.num_ids, ed1);
287 bsendmsg(ua, _("Pruned Files from %s Jobs for client %s from catalog.\n"),
288 ed1, client->hdr.name);
295 free_pool_memory(query);
300 static void drop_temp_tables(UAContext *ua)
303 for (i=0; drop_deltabs[i]; i++) {
304 db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
308 static bool create_temp_tables(UAContext *ua)
311 /* Create temp tables and indicies */
312 for (i=0; create_deltabs[i]; i++) {
313 if (!db_sql_query(ua->db, create_deltabs[i], NULL, (void *)NULL)) {
314 bsendmsg(ua, "%s", db_strerror(ua->db));
315 Dmsg0(050, "create DelTables table failed\n");
325 * Purging Jobs is a bit more complicated than purging Files
326 * because we delete Job records only if there is a more current
327 * backup of the FileSet. Otherwise, we keep the Job record.
328 * In other words, we never delete the only Job record that
329 * contains a current backup of a FileSet. This prevents the
330 * Volume from being recycled and destroying a current backup.
332 * For Verify Jobs, we do not delete the last InitCatalog.
334 * For Restore Jobs there are no restrictions.
336 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
338 struct s_job_del_ctx del;
339 struct s_count_ctx cnt;
340 POOLMEM *query = get_pool_memory(PM_MESSAGE);
344 char ed1[50], ed2[50];
347 memset(&cr, 0, sizeof(cr));
348 memset(&del, 0, sizeof(del));
349 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
350 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
355 period = client->JobRetention;
356 now = (utime_t)time(NULL);
358 /* Drop any previous temporary tables still there */
359 drop_temp_tables(ua);
361 /* Create temp tables and indicies */
362 if (!create_temp_tables(ua)) {
367 * Select all files that are older than the JobRetention period
368 * and stuff them into the "DeletionCandidates" table.
370 edit_uint64(now - period, ed1);
371 Mmsg(query, insert_delcand, (char)JobType, ed1,
372 edit_int64(cr.ClientId, ed2));
373 if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
375 bsendmsg(ua, "%s", db_strerror(ua->db));
377 Dmsg0(050, "insert delcand failed\n");
381 /* Count Files to be deleted */
382 pm_strcpy(query, cnt_DelCand);
383 Dmsg1(100, "select sql=%s\n", query);
385 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
386 bsendmsg(ua, "%s", db_strerror(ua->db));
387 Dmsg0(050, "Count failed\n");
391 if (cnt.count == 0) {
393 bsendmsg(ua, _("No Jobs found to prune.\n"));
398 if (cnt.count < MAX_DEL_LIST_LEN) {
399 del.max_ids = cnt.count + 1;
401 del.max_ids = MAX_DEL_LIST_LEN;
403 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
404 del.PurgedFiles = (char *)malloc(del.max_ids);
407 edit_int64(cr.ClientId, ed2);
410 Mmsg(query, select_backup_del, ed1, ed1, ed2);
413 Mmsg(query, select_restore_del, ed1, ed1, ed2);
416 Mmsg(query, select_verify_del, ed1, ed1, ed2);
419 Mmsg(query, select_admin_del, ed1, ed1, ed2);
422 if (!db_sql_query(ua->db, query, job_delete_handler, (void *)&del)) {
423 bsendmsg(ua, "%s", db_strerror(ua->db));
427 * OK, now we have the list of JobId's to be pruned, first check
428 * if the Files have been purged, if not, purge (delete) them.
429 * Then delete the Job entry, and finally and JobMedia records.
431 for (i=0; i < del.num_ids; i++) {
432 edit_int64(del.JobId[i], ed1);
433 Dmsg1(050, "Delete JobId=%s\n", ed1);
434 if (!del.PurgedFiles[i]) {
435 Mmsg(query, del_File, ed1);
436 if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
437 bsendmsg(ua, "%s", db_strerror(ua->db));
439 Dmsg1(050, "Del sql=%s\n", query);
442 Mmsg(query, del_Job, ed1);
443 if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
444 bsendmsg(ua, "%s", db_strerror(ua->db));
446 Dmsg1(050, "Del sql=%s\n", query);
448 Mmsg(query, del_JobMedia, ed1);
449 if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
450 bsendmsg(ua, "%s", db_strerror(ua->db));
452 Dmsg1(050, "Del sql=%s\n", query);
454 bsendmsg(ua, _("Pruned %d %s for client %s from catalog.\n"), del.num_ids,
455 del.num_ids==1?_("Job"):_("Jobs"), client->hdr.name);
458 drop_temp_tables(ua);
463 if (del.PurgedFiles) {
464 free(del.PurgedFiles);
466 free_pool_memory(query);
471 * Prune a given Volume
473 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
475 POOLMEM *query = get_pool_memory(PM_MESSAGE);
476 struct s_count_ctx cnt;
477 struct s_file_del_ctx del;
484 if (mr->Enabled == 2) {
485 return false; /* Cannot prune archived volumes */
489 memset(&jr, 0, sizeof(jr));
490 memset(&del, 0, sizeof(del));
493 * Find out how many Jobs remain on this Volume by
494 * counting the JobMedia records.
497 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
498 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
499 bsendmsg(ua, "%s", db_strerror(ua->db));
500 Dmsg0(050, "Count failed\n");
504 if (cnt.count == 0) {
505 /* Don't mark appendable volume as purged */
506 if (strcmp(mr->VolStatus, "Append") == 0 && verbose) {
507 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Prune not needed.\n"),
512 /* If volume not already purged, do so */
513 if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
514 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
517 ok = mark_media_purged(ua, mr);
521 if (cnt.count < MAX_DEL_LIST_LEN) {
522 del.max_ids = cnt.count + 1;
524 del.max_ids = MAX_DEL_LIST_LEN;
528 * Now get a list of JobIds for Jobs written to this Volume
529 * Could optimize here by adding JobTDate > (now - period).
531 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
532 Mmsg(query, sel_JobMedia, edit_int64(mr->MediaId, ed1));
533 if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
535 bsendmsg(ua, "%s", db_strerror(ua->db));
537 Dmsg0(050, "Count failed\n");
541 /* Use Volume Retention to prune Jobs and their Files */
542 period = mr->VolRetention;
543 now = (utime_t)time(NULL);
545 Dmsg3(200, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
548 for (i=0; i < del.num_ids; i++) {
549 jr.JobId = del.JobId[i];
550 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
553 Dmsg2(200, "Looking at %s JobTdate=%d\n", jr.Job, (int)jr.JobTDate);
554 if (jr.JobTDate >= (now - period)) {
557 edit_int64(del.JobId[i], ed1);
558 Dmsg2(200, "Delete JobId=%s Job=%s\n", ed1, jr.Job);
559 Mmsg(query, del_File, ed1);
560 db_sql_query(ua->db, query, NULL, (void *)NULL);
561 Mmsg(query, del_Job, ed1);
562 db_sql_query(ua->db, query, NULL, (void *)NULL);
563 Mmsg(query, del_JobMedia, ed1);
564 db_sql_query(ua->db, query, NULL, (void *)NULL);
565 Mmsg(query, del_MAC, ed1);
566 db_sql_query(ua->db, query, NULL, (void *)NULL);
567 Dmsg1(050, "Del sql=%s\n", query);
573 if (ua->verbose && del.num_del != 0) {
574 bsendmsg(ua, _("Pruned %d %s on Volume \"%s\" from catalog.\n"), del.num_del,
575 del.num_del == 1 ? "Job" : "Jobs", mr->VolumeName);
579 * Find out how many Jobs remain on this Volume by
580 * counting the JobMedia records.
583 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
584 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
585 bsendmsg(ua, "%s", db_strerror(ua->db));
586 Dmsg0(050, "Count failed\n");
589 if (cnt.count == 0) {
590 Dmsg0(200, "Volume is purged.\n");
591 ok = mark_media_purged(ua, mr);
596 free_pool_memory(query);