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 /* In memory list of JobIds */
36 struct s_file_del_ctx {
38 int num_ids; /* ids stored */
39 int max_ids; /* size of array */
40 int num_del; /* number deleted */
41 int tot_ids; /* total to process */
44 struct s_job_del_ctx {
45 JobId_t *JobId; /* array of JobIds */
46 char *PurgedFiles; /* Array of PurgedFile flags */
47 int num_ids; /* ids stored */
48 int max_ids; /* size of array */
49 int num_del; /* number deleted */
50 int tot_ids; /* total to process */
59 * Called here to count entries to be deleted
61 static int count_handler(void *ctx, int num_fields, char **row)
63 struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
66 cnt->count = str_to_int64(row[0]);
75 * Called here to count the number of Jobs to be pruned
77 static int file_count_handler(void *ctx, int num_fields, char **row)
79 struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
86 * Called here to make in memory list of JobIds to be
87 * deleted and the associated PurgedFiles flag.
88 * The in memory list will then be transversed
89 * to issue the SQL DELETE commands. Note, the list
90 * is allowed to get to MAX_DEL_LIST_LEN to limit the
91 * maximum malloc'ed memory.
93 static int job_delete_handler(void *ctx, int num_fields, char **row)
95 struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
97 if (del->num_ids == MAX_DEL_LIST_LEN) {
100 if (del->num_ids == del->max_ids) {
101 del->max_ids = (del->max_ids * 3) / 2;
102 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
103 del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
105 del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
106 del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[1]);
110 static int file_delete_handler(void *ctx, int num_fields, char **row)
112 struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
114 if (del->num_ids == MAX_DEL_LIST_LEN) {
117 if (del->num_ids == del->max_ids) {
118 del->max_ids = (del->max_ids * 3) / 2;
119 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
122 del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
127 * Prune records from database
129 * prune files (from) client=xxx
130 * prune jobs (from) client=xxx
133 int prunecmd(UAContext *ua, const char *cmd)
140 static const char *keywords[] = {
150 /* First search args */
151 kw = find_arg_keyword(ua, keywords);
152 if (kw < 0 || kw > 2) {
153 /* no args, so ask user */
154 kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);
158 case 0: /* prune files */
159 client = get_client_resource(ua);
160 if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
163 prune_files(ua, client);
165 case 1: /* prune jobs */
166 client = get_client_resource(ua);
167 if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
170 /* ****FIXME**** allow user to select JobType */
171 prune_jobs(ua, client, JT_BACKUP);
173 case 2: /* prune volume */
174 if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
177 if (mr.Enabled == 2) {
178 bsendmsg(ua, _("Cannot prune Volume \"%s\" because it is archived.\n"),
181 if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
184 prune_volume(ua, &mr);
194 * Prune File records from the database. For any Job which
195 * is older than the retention period, we unconditionally delete
196 * all File records for that Job. This is simple enough that no
197 * temporary tables are needed. We simply make an in memory list of
198 * the JobIds meeting the prune conditions, then delete all File records
199 * pointing to each of those JobIds.
201 * This routine assumes you want the pruning to be done. All checking
202 * must be done before calling this routine.
204 int prune_files(UAContext *ua, CLIENT *client)
206 struct s_file_del_ctx del;
207 POOLMEM *query = get_pool_memory(PM_MESSAGE);
211 char ed1[50], ed2[50];
214 memset(&cr, 0, sizeof(cr));
215 memset(&del, 0, sizeof(del));
216 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
217 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
222 period = client->FileRetention;
223 now = (utime_t)time(NULL);
225 /* Select Jobs -- for counting */
226 Mmsg(query, select_job, edit_uint64(now - period, ed1),
227 edit_int64(cr.ClientId, ed2));
228 Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, (uint32_t)period, query);
229 if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
231 bsendmsg(ua, "%s", db_strerror(ua->db));
233 Dmsg0(050, "Count failed\n");
237 if (del.tot_ids == 0) {
239 bsendmsg(ua, _("No Files found to prune.\n"));
244 if (del.tot_ids < MAX_DEL_LIST_LEN) {
245 del.max_ids = del.tot_ids + 1;
247 del.max_ids = MAX_DEL_LIST_LEN;
251 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
253 /* Now process same set but making a delete list */
254 db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
256 for (i=0; i < del.num_ids; i++) {
257 purge_files_from_job(ua, del.JobId[i]);
259 edit_uint64_with_commas(del.num_ids, ed1);
260 bsendmsg(ua, _("Pruned Files from %s Jobs for client %s from catalog.\n"),
261 ed1, client->hdr.name);
268 free_pool_memory(query);
273 static void drop_temp_tables(UAContext *ua)
276 for (i=0; drop_deltabs[i]; i++) {
277 db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
281 static bool create_temp_tables(UAContext *ua)
284 /* Create temp tables and indicies */
285 for (i=0; create_deltabs[i]; i++) {
286 if (!db_sql_query(ua->db, create_deltabs[i], NULL, (void *)NULL)) {
287 bsendmsg(ua, "%s", db_strerror(ua->db));
288 Dmsg0(050, "create DelTables table failed\n");
298 * Pruning Jobs is a bit more complicated than purging Files
299 * because we delete Job records only if there is a more current
300 * backup of the FileSet. Otherwise, we keep the Job record.
301 * In other words, we never delete the only Job record that
302 * contains a current backup of a FileSet. This prevents the
303 * Volume from being recycled and destroying a current backup.
305 * For Verify Jobs, we do not delete the last InitCatalog.
307 * For Restore Jobs there are no restrictions.
309 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
311 struct s_job_del_ctx del;
312 struct s_count_ctx cnt;
313 POOLMEM *query = get_pool_memory(PM_MESSAGE);
317 char ed1[50], ed2[50];
320 memset(&cr, 0, sizeof(cr));
321 memset(&del, 0, sizeof(del));
322 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
323 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
328 period = client->JobRetention;
329 now = (utime_t)time(NULL);
331 /* Drop any previous temporary tables still there */
332 drop_temp_tables(ua);
334 /* Create temp tables and indicies */
335 if (!create_temp_tables(ua)) {
340 * Select all files that are older than the JobRetention period
341 * and stuff them into the "DeletionCandidates" table.
343 edit_uint64(now - period, ed1);
344 Mmsg(query, insert_delcand, (char)JobType, ed1,
345 edit_int64(cr.ClientId, ed2));
346 if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
348 bsendmsg(ua, "%s", db_strerror(ua->db));
350 Dmsg0(050, "insert delcand failed\n");
354 /* Count Files to be deleted */
355 pm_strcpy(query, cnt_DelCand);
356 Dmsg1(100, "select sql=%s\n", query);
358 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
359 bsendmsg(ua, "%s", db_strerror(ua->db));
360 Dmsg0(050, "Count failed\n");
364 if (cnt.count == 0) {
366 bsendmsg(ua, _("No Jobs found to prune.\n"));
371 if (cnt.count < MAX_DEL_LIST_LEN) {
372 del.max_ids = cnt.count + 1;
374 del.max_ids = MAX_DEL_LIST_LEN;
376 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
377 del.PurgedFiles = (char *)malloc(del.max_ids);
380 edit_int64(cr.ClientId, ed2);
383 Mmsg(query, select_backup_del, ed1, ed1, ed2);
386 Mmsg(query, select_restore_del, ed1, ed1, ed2);
389 Mmsg(query, select_verify_del, ed1, ed1, ed2);
392 Mmsg(query, select_admin_del, ed1, ed1, ed2);
395 Mmsg(query, select_migrate_del, ed1, ed1, ed2);
398 if (!db_sql_query(ua->db, query, job_delete_handler, (void *)&del)) {
399 bsendmsg(ua, "%s", db_strerror(ua->db));
403 * OK, now we have the list of JobId's to be pruned, first check
404 * if the Files have been purged, if not, purge (delete) them.
405 * Then delete the Job entry, and finally and JobMedia records.
407 for (i=0; i < del.num_ids; i++) {
408 if (!del.PurgedFiles[i]) {
409 purge_files_from_job(ua, del.JobId[i]);
411 purge_job_from_catalog(ua, del.JobId[i]);
413 bsendmsg(ua, _("Pruned %d %s for client %s from catalog.\n"), del.num_ids,
414 del.num_ids==1?_("Job"):_("Jobs"), client->hdr.name);
417 drop_temp_tables(ua);
422 if (del.PurgedFiles) {
423 free(del.PurgedFiles);
425 free_pool_memory(query);
430 * Prune a given Volume
432 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
434 POOLMEM *query = get_pool_memory(PM_MESSAGE);
435 struct s_count_ctx cnt;
436 struct s_file_del_ctx del;
443 if (mr->Enabled == 2) {
444 return false; /* Cannot prune archived volumes */
448 memset(&jr, 0, sizeof(jr));
449 memset(&del, 0, sizeof(del));
452 * Find out how many Jobs remain on this Volume by
453 * counting the JobMedia records.
456 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
457 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
458 bsendmsg(ua, "%s", db_strerror(ua->db));
459 Dmsg0(050, "Count failed\n");
463 if (cnt.count == 0) {
464 /* Don't mark appendable volume as purged */
465 if (strcmp(mr->VolStatus, "Append") == 0 && verbose) {
466 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Prune not needed.\n"),
471 /* If volume not already purged, do so */
472 if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
473 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
476 ok = mark_media_purged(ua, mr);
480 if (cnt.count < MAX_DEL_LIST_LEN) {
481 del.max_ids = cnt.count + 1;
483 del.max_ids = MAX_DEL_LIST_LEN;
487 * Now get a list of JobIds for Jobs written to this Volume
488 * Could optimize here by adding JobTDate > (now - period).
490 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
491 Mmsg(query, sel_JobMedia, edit_int64(mr->MediaId, ed1));
492 if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
494 bsendmsg(ua, "%s", db_strerror(ua->db));
496 Dmsg0(050, "Count failed\n");
500 /* Use Volume Retention to prune Jobs and their Files */
501 period = mr->VolRetention;
502 now = (utime_t)time(NULL);
504 Dmsg3(200, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
507 for (i=0; i < del.num_ids; i++) {
508 jr.JobId = del.JobId[i];
509 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
512 Dmsg2(200, "Looking at %s JobTdate=%d\n", jr.Job, (int)jr.JobTDate);
513 if (jr.JobTDate >= (now - period)) {
516 purge_files_from_job(ua, del.JobId[i]);
517 purge_job_from_catalog(ua, del.JobId[i]);
523 if (ua->verbose && del.num_del != 0) {
524 bsendmsg(ua, _("Pruned %d %s on Volume \"%s\" from catalog.\n"), del.num_del,
525 del.num_del == 1 ? "Job" : "Jobs", mr->VolumeName);
529 * Find out how many Jobs remain on this Volume by
530 * counting the JobMedia records.
533 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
534 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
535 bsendmsg(ua, "%s", db_strerror(ua->db));
536 Dmsg0(050, "Count failed\n");
539 if (cnt.count == 0) {
540 Dmsg0(200, "Volume is purged.\n");
541 ok = mark_media_purged(ua, mr);
546 free_pool_memory(query);