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 (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
180 prune_volume(ua, &mr);
190 * Prune File records from the database. For any Job which
191 * is older than the retention period, we unconditionally delete
192 * all File records for that Job. This is simple enough that no
193 * temporary tables are needed. We simply make an in memory list of
194 * the JobIds meeting the prune conditions, then delete all File records
195 * pointing to each of those JobIds.
197 * This routine assumes you want the pruning to be done. All checking
198 * must be done before calling this routine.
200 int prune_files(UAContext *ua, CLIENT *client)
202 struct s_file_del_ctx del;
203 POOLMEM *query = get_pool_memory(PM_MESSAGE);
207 char ed1[50], ed2[50];
210 memset(&cr, 0, sizeof(cr));
211 memset(&del, 0, sizeof(del));
212 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
213 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
218 period = client->FileRetention;
219 now = (utime_t)time(NULL);
221 /* Select Jobs -- for counting */
222 Mmsg(query, select_job, edit_uint64(now - period, ed1),
223 edit_int64(cr.ClientId, ed2));
224 Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, (uint32_t)period, query);
225 if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
227 bsendmsg(ua, "%s", db_strerror(ua->db));
229 Dmsg0(050, "Count failed\n");
233 if (del.tot_ids == 0) {
235 bsendmsg(ua, _("No Files found to prune.\n"));
240 if (del.tot_ids < MAX_DEL_LIST_LEN) {
241 del.max_ids = del.tot_ids + 1;
243 del.max_ids = MAX_DEL_LIST_LEN;
247 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
249 /* Now process same set but making a delete list */
250 db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
252 for (i=0; i < del.num_ids; i++) {
253 purge_files_from_job(ua, del.JobId[i]);
255 edit_uint64_with_commas(del.num_ids, ed1);
256 bsendmsg(ua, _("Pruned Files from %s Jobs for client %s from catalog.\n"),
257 ed1, client->hdr.name);
264 free_pool_memory(query);
269 static void drop_temp_tables(UAContext *ua)
272 for (i=0; drop_deltabs[i]; i++) {
273 db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
277 static bool create_temp_tables(UAContext *ua)
280 /* Create temp tables and indicies */
281 for (i=0; create_deltabs[i]; i++) {
282 if (!db_sql_query(ua->db, create_deltabs[i], NULL, (void *)NULL)) {
283 bsendmsg(ua, "%s", db_strerror(ua->db));
284 Dmsg0(050, "create DelTables table failed\n");
294 * Purging Jobs is a bit more complicated than purging Files
295 * because we delete Job records only if there is a more current
296 * backup of the FileSet. Otherwise, we keep the Job record.
297 * In other words, we never delete the only Job record that
298 * contains a current backup of a FileSet. This prevents the
299 * Volume from being recycled and destroying a current backup.
301 * For Verify Jobs, we do not delete the last InitCatalog.
303 * For Restore Jobs there are no restrictions.
305 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
307 struct s_job_del_ctx del;
308 struct s_count_ctx cnt;
309 POOLMEM *query = get_pool_memory(PM_MESSAGE);
313 char ed1[50], ed2[50];
316 memset(&cr, 0, sizeof(cr));
317 memset(&del, 0, sizeof(del));
318 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
319 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
324 period = client->JobRetention;
325 now = (utime_t)time(NULL);
327 /* Drop any previous temporary tables still there */
328 drop_temp_tables(ua);
330 /* Create temp tables and indicies */
331 if (!create_temp_tables(ua)) {
336 * Select all files that are older than the JobRetention period
337 * and stuff them into the "DeletionCandidates" table.
339 edit_uint64(now - period, ed1);
340 Mmsg(query, insert_delcand, (char)JobType, ed1,
341 edit_int64(cr.ClientId, ed2));
342 if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
344 bsendmsg(ua, "%s", db_strerror(ua->db));
346 Dmsg0(050, "insert delcand failed\n");
350 /* Count Files to be deleted */
351 pm_strcpy(query, cnt_DelCand);
352 Dmsg1(100, "select sql=%s\n", query);
354 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
355 bsendmsg(ua, "%s", db_strerror(ua->db));
356 Dmsg0(050, "Count failed\n");
360 if (cnt.count == 0) {
362 bsendmsg(ua, _("No Jobs found to prune.\n"));
367 if (cnt.count < MAX_DEL_LIST_LEN) {
368 del.max_ids = cnt.count + 1;
370 del.max_ids = MAX_DEL_LIST_LEN;
372 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
373 del.PurgedFiles = (char *)malloc(del.max_ids);
376 edit_int64(cr.ClientId, ed2);
379 Mmsg(query, select_backup_del, ed1, ed1, ed2);
382 Mmsg(query, select_restore_del, ed1, ed1, ed2);
385 Mmsg(query, select_verify_del, ed1, ed1, ed2);
388 Mmsg(query, select_admin_del, ed1, ed1, ed2);
391 if (!db_sql_query(ua->db, query, job_delete_handler, (void *)&del)) {
392 bsendmsg(ua, "%s", db_strerror(ua->db));
396 * OK, now we have the list of JobId's to be pruned, first check
397 * if the Files have been purged, if not, purge (delete) them.
398 * Then delete the Job entry, and finally and JobMedia records.
400 for (i=0; i < del.num_ids; i++) {
401 if (!del.PurgedFiles[i]) {
402 purge_files_from_job(ua, del.JobId[i]);
404 purge_job_from_catalog(ua, del.JobId[i]);
406 bsendmsg(ua, _("Pruned %d %s for client %s from catalog.\n"), del.num_ids,
407 del.num_ids==1?_("Job"):_("Jobs"), client->hdr.name);
410 drop_temp_tables(ua);
415 if (del.PurgedFiles) {
416 free(del.PurgedFiles);
418 free_pool_memory(query);
423 * Prune a given Volume
425 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
427 POOLMEM *query = get_pool_memory(PM_MESSAGE);
428 struct s_count_ctx cnt;
429 struct s_file_del_ctx del;
436 if (mr->Enabled == 2) {
437 return false; /* Cannot prune archived volumes */
441 memset(&jr, 0, sizeof(jr));
442 memset(&del, 0, sizeof(del));
445 * Find out how many Jobs remain on this Volume by
446 * counting the JobMedia records.
449 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
450 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
451 bsendmsg(ua, "%s", db_strerror(ua->db));
452 Dmsg0(050, "Count failed\n");
456 if (cnt.count == 0) {
457 /* Don't mark appendable volume as purged */
458 if (strcmp(mr->VolStatus, "Append") == 0 && verbose) {
459 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Prune not needed.\n"),
464 /* If volume not already purged, do so */
465 if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
466 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
469 ok = mark_media_purged(ua, mr);
473 if (cnt.count < MAX_DEL_LIST_LEN) {
474 del.max_ids = cnt.count + 1;
476 del.max_ids = MAX_DEL_LIST_LEN;
480 * Now get a list of JobIds for Jobs written to this Volume
481 * Could optimize here by adding JobTDate > (now - period).
483 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
484 Mmsg(query, sel_JobMedia, edit_int64(mr->MediaId, ed1));
485 if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
487 bsendmsg(ua, "%s", db_strerror(ua->db));
489 Dmsg0(050, "Count failed\n");
493 /* Use Volume Retention to prune Jobs and their Files */
494 period = mr->VolRetention;
495 now = (utime_t)time(NULL);
497 Dmsg3(200, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
500 for (i=0; i < del.num_ids; i++) {
501 jr.JobId = del.JobId[i];
502 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
505 Dmsg2(200, "Looking at %s JobTdate=%d\n", jr.Job, (int)jr.JobTDate);
506 if (jr.JobTDate >= (now - period)) {
509 purge_files_from_job(ua, del.JobId[i]);
510 purge_job_from_catalog(ua, del.JobId[i]);
516 if (ua->verbose && del.num_del != 0) {
517 bsendmsg(ua, _("Pruned %d %s on Volume \"%s\" from catalog.\n"), del.num_del,
518 del.num_del == 1 ? "Job" : "Jobs", mr->VolumeName);
522 * Find out how many Jobs remain on this Volume by
523 * counting the JobMedia records.
526 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
527 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
528 bsendmsg(ua, "%s", db_strerror(ua->db));
529 Dmsg0(050, "Count failed\n");
532 if (cnt.count == 0) {
533 Dmsg0(200, "Volume is purged.\n");
534 ok = mark_media_purged(ua, mr);
539 free_pool_memory(query);