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[0]);
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 = (char *)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 int prune_volume(UAContext *ua, MEDIA_DBR *mr)
475 POOLMEM *query = (char *)get_pool_memory(PM_MESSAGE);
476 struct s_count_ctx cnt;
477 struct s_file_del_ctx del;
484 memset(&jr, 0, sizeof(jr));
485 memset(&del, 0, sizeof(del));
488 * Find out how many Jobs remain on this Volume by
489 * counting the JobMedia records.
492 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
493 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
494 bsendmsg(ua, "%s", db_strerror(ua->db));
495 Dmsg0(050, "Count failed\n");
499 if (cnt.count == 0) {
500 /* Don't mark appendable volume as purged */
501 if (strcmp(mr->VolStatus, "Append") == 0 && verbose) {
502 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Prune not needed.\n"),
507 /* If volume not already purged, do so */
508 if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
509 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
512 stat = mark_media_purged(ua, mr);
516 if (cnt.count < MAX_DEL_LIST_LEN) {
517 del.max_ids = cnt.count + 1;
519 del.max_ids = MAX_DEL_LIST_LEN;
523 * Now get a list of JobIds for Jobs written to this Volume
524 * Could optimize here by adding JobTDate > (now - period).
526 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
527 Mmsg(query, sel_JobMedia, edit_int64(mr->MediaId, ed1));
528 if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
530 bsendmsg(ua, "%s", db_strerror(ua->db));
532 Dmsg0(050, "Count failed\n");
536 /* Use Volume Retention to prune Jobs and their Files */
537 period = mr->VolRetention;
538 now = (utime_t)time(NULL);
540 Dmsg3(200, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
543 for (i=0; i < del.num_ids; i++) {
544 jr.JobId = del.JobId[i];
545 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
548 Dmsg2(200, "Looking at %s JobTdate=%d\n", jr.Job, (int)jr.JobTDate);
549 if (jr.JobTDate >= (now - period)) {
552 edit_int64(del.JobId[i], ed1);
553 Dmsg2(200, "Delete JobId=%s Job=%s\n", ed1, jr.Job);
554 Mmsg(query, del_File, ed1);
555 db_sql_query(ua->db, query, NULL, (void *)NULL);
556 Mmsg(query, del_Job, ed1);
557 db_sql_query(ua->db, query, NULL, (void *)NULL);
558 Mmsg(query, del_JobMedia, ed1);
559 db_sql_query(ua->db, query, NULL, (void *)NULL);
560 Mmsg(query, del_MAC, ed1);
561 db_sql_query(ua->db, query, NULL, (void *)NULL);
562 Dmsg1(050, "Del sql=%s\n", query);
568 if (ua->verbose && del.num_del != 0) {
569 bsendmsg(ua, _("Pruned %d %s on Volume \"%s\" from catalog.\n"), del.num_del,
570 del.num_del == 1 ? "Job" : "Jobs", mr->VolumeName);
574 * Find out how many Jobs remain on this Volume by
575 * counting the JobMedia records.
578 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
579 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
580 bsendmsg(ua, "%s", db_strerror(ua->db));
581 Dmsg0(050, "Count failed\n");
584 if (cnt.count == 0) {
585 Dmsg0(200, "Volume is purged.\n");
586 stat = mark_media_purged(ua, mr);
591 free_pool_memory(query);