3 * Bacula Director -- User Agent Database prune Command
4 * Applies retention periods
6 * Kern Sibbald, February MMII
11 Copyright (C) 2002-2005 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 ammended 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 */
29 int mark_media_purged(UAContext *ua, MEDIA_DBR *mr);
31 /* Forward referenced functions */
34 #define MAX_DEL_LIST_LEN 1000000
36 /* Imported variables */
37 extern char *select_job;
38 extern char *drop_deltabs[];
39 extern char *create_deltabs[];
40 extern char *insert_delcand;
41 extern char *select_backup_del;
42 extern char *select_verify_del;
43 extern char *select_restore_del;
44 extern char *select_admin_del;
45 extern char *cnt_File;
46 extern char *del_File;
47 extern char *upd_Purged;
48 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 Dmsg1(050, "select sql=%s\n", 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(050, "Delete 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(050, "Del 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 int 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 if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
501 bsendmsg(ua, "There are no Jobs associated with Volume \"%s\". Marking it purged.\n",
504 stat = mark_media_purged(ua, mr);
508 if (cnt.count < MAX_DEL_LIST_LEN) {
509 del.max_ids = cnt.count + 1;
511 del.max_ids = MAX_DEL_LIST_LEN;
515 * Now get a list of JobIds for Jobs written to this Volume
516 * Could optimize here by adding JobTDate > (now - period).
518 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
519 Mmsg(query, sel_JobMedia, edit_int64(mr->MediaId, ed1));
520 if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
522 bsendmsg(ua, "%s", db_strerror(ua->db));
524 Dmsg0(050, "Count failed\n");
528 /* Use Volume Retention to prune Jobs and their Files */
529 period = mr->VolRetention;
530 now = (utime_t)time(NULL);
532 Dmsg3(200, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
535 for (i=0; i < del.num_ids; i++) {
536 jr.JobId = del.JobId[i];
537 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
540 Dmsg2(200, "Looking at %s JobTdate=%d\n", jr.Job, (int)jr.JobTDate);
541 if (jr.JobTDate >= (now - period)) {
544 edit_int64(del.JobId[i], ed1);
545 Dmsg2(200, "Delete JobId=%s Job=%s\n", ed1, jr.Job);
546 Mmsg(query, del_File, ed1);
547 db_sql_query(ua->db, query, NULL, (void *)NULL);
548 Mmsg(query, del_Job, ed1);
549 db_sql_query(ua->db, query, NULL, (void *)NULL);
550 Mmsg(query, del_JobMedia, ed1);
551 db_sql_query(ua->db, query, NULL, (void *)NULL);
552 Dmsg1(050, "Del sql=%s\n", query);
558 if (ua->verbose && del.num_del != 0) {
559 bsendmsg(ua, _("Pruned %d %s on Volume \"%s\" from catalog.\n"), del.num_del,
560 del.num_del == 1 ? "Job" : "Jobs", mr->VolumeName);
563 /* If purged, mark it so */
564 if (del.num_ids == del.num_del) {
565 Dmsg0(200, "Volume is purged.\n");
566 stat = mark_media_purged(ua, mr);
571 free_pool_memory(query);