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 const char *select_job;
37 extern const char *drop_deltabs[];
38 extern const char *create_deltabs[];
39 extern const char *insert_delcand;
40 extern const char *select_backup_del;
41 extern const char *select_verify_del;
42 extern const char *select_restore_del;
43 extern const char *select_admin_del;
44 extern const char *cnt_File;
45 extern const char *cnt_DelCand;
46 extern const char *del_Job;
47 extern const char *del_MAC;
48 extern const char *del_JobMedia;
49 extern const char *cnt_JobMedia;
50 extern const char *sel_JobMedia;
53 /* In memory list of JobIds */
54 struct s_file_del_ctx {
56 int num_ids; /* ids stored */
57 int max_ids; /* size of array */
58 int num_del; /* number deleted */
59 int tot_ids; /* total to process */
62 struct s_job_del_ctx {
63 JobId_t *JobId; /* array of JobIds */
64 char *PurgedFiles; /* Array of PurgedFile flags */
65 int num_ids; /* ids stored */
66 int max_ids; /* size of array */
67 int num_del; /* number deleted */
68 int tot_ids; /* total to process */
77 * Called here to count entries to be deleted
79 static int count_handler(void *ctx, int num_fields, char **row)
81 struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
84 cnt->count = str_to_int64(row[0]);
93 * Called here to count the number of Jobs to be pruned
95 static int file_count_handler(void *ctx, int num_fields, char **row)
97 struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
104 * Called here to make in memory list of JobIds to be
105 * deleted and the associated PurgedFiles flag.
106 * The in memory list will then be transversed
107 * to issue the SQL DELETE commands. Note, the list
108 * is allowed to get to MAX_DEL_LIST_LEN to limit the
109 * maximum malloc'ed memory.
111 static int job_delete_handler(void *ctx, int num_fields, char **row)
113 struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
115 if (del->num_ids == MAX_DEL_LIST_LEN) {
118 if (del->num_ids == del->max_ids) {
119 del->max_ids = (del->max_ids * 3) / 2;
120 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
121 del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
123 del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
124 del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[1]);
128 static int file_delete_handler(void *ctx, int num_fields, char **row)
130 struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
132 if (del->num_ids == MAX_DEL_LIST_LEN) {
135 if (del->num_ids == del->max_ids) {
136 del->max_ids = (del->max_ids * 3) / 2;
137 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
140 del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
145 * Prune records from database
147 * prune files (from) client=xxx
148 * prune jobs (from) client=xxx
151 int prunecmd(UAContext *ua, const char *cmd)
158 static const char *keywords[] = {
168 /* First search args */
169 kw = find_arg_keyword(ua, keywords);
170 if (kw < 0 || kw > 2) {
171 /* no args, so ask user */
172 kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);
176 case 0: /* prune files */
177 client = get_client_resource(ua);
178 if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
181 prune_files(ua, client);
183 case 1: /* prune jobs */
184 client = get_client_resource(ua);
185 if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
188 /* ****FIXME**** allow user to select JobType */
189 prune_jobs(ua, client, JT_BACKUP);
191 case 2: /* prune volume */
192 if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
195 if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
198 prune_volume(ua, &mr);
208 * Prune File records from the database. For any Job which
209 * is older than the retention period, we unconditionally delete
210 * all File records for that Job. This is simple enough that no
211 * temporary tables are needed. We simply make an in memory list of
212 * the JobIds meeting the prune conditions, then delete all File records
213 * pointing to each of those JobIds.
215 * This routine assumes you want the pruning to be done. All checking
216 * must be done before calling this routine.
218 int prune_files(UAContext *ua, CLIENT *client)
220 struct s_file_del_ctx del;
221 POOLMEM *query = get_pool_memory(PM_MESSAGE);
225 char ed1[50], ed2[50];
228 memset(&cr, 0, sizeof(cr));
229 memset(&del, 0, sizeof(del));
230 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
231 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
236 period = client->FileRetention;
237 now = (utime_t)time(NULL);
239 /* Select Jobs -- for counting */
240 Mmsg(query, select_job, edit_uint64(now - period, ed1),
241 edit_int64(cr.ClientId, ed2));
242 Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, (uint32_t)period, query);
243 if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
245 bsendmsg(ua, "%s", db_strerror(ua->db));
247 Dmsg0(050, "Count failed\n");
251 if (del.tot_ids == 0) {
253 bsendmsg(ua, _("No Files found to prune.\n"));
258 if (del.tot_ids < MAX_DEL_LIST_LEN) {
259 del.max_ids = del.tot_ids + 1;
261 del.max_ids = MAX_DEL_LIST_LEN;
265 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
267 /* Now process same set but making a delete list */
268 db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
270 for (i=0; i < del.num_ids; i++) {
271 purge_files_from_job(ua, del.JobId[i]);
273 edit_uint64_with_commas(del.num_ids, ed1);
274 bsendmsg(ua, _("Pruned Files from %s Jobs for client %s from catalog.\n"),
275 ed1, client->hdr.name);
282 free_pool_memory(query);
287 static void drop_temp_tables(UAContext *ua)
290 for (i=0; drop_deltabs[i]; i++) {
291 db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
295 static bool create_temp_tables(UAContext *ua)
298 /* Create temp tables and indicies */
299 for (i=0; create_deltabs[i]; i++) {
300 if (!db_sql_query(ua->db, create_deltabs[i], NULL, (void *)NULL)) {
301 bsendmsg(ua, "%s", db_strerror(ua->db));
302 Dmsg0(050, "create DelTables table failed\n");
312 * Purging Jobs is a bit more complicated than purging Files
313 * because we delete Job records only if there is a more current
314 * backup of the FileSet. Otherwise, we keep the Job record.
315 * In other words, we never delete the only Job record that
316 * contains a current backup of a FileSet. This prevents the
317 * Volume from being recycled and destroying a current backup.
319 * For Verify Jobs, we do not delete the last InitCatalog.
321 * For Restore Jobs there are no restrictions.
323 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
325 struct s_job_del_ctx del;
326 struct s_count_ctx cnt;
327 POOLMEM *query = get_pool_memory(PM_MESSAGE);
331 char ed1[50], ed2[50];
334 memset(&cr, 0, sizeof(cr));
335 memset(&del, 0, sizeof(del));
336 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
337 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
342 period = client->JobRetention;
343 now = (utime_t)time(NULL);
345 /* Drop any previous temporary tables still there */
346 drop_temp_tables(ua);
348 /* Create temp tables and indicies */
349 if (!create_temp_tables(ua)) {
354 * Select all files that are older than the JobRetention period
355 * and stuff them into the "DeletionCandidates" table.
357 edit_uint64(now - period, ed1);
358 Mmsg(query, insert_delcand, (char)JobType, ed1,
359 edit_int64(cr.ClientId, ed2));
360 if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
362 bsendmsg(ua, "%s", db_strerror(ua->db));
364 Dmsg0(050, "insert delcand failed\n");
368 /* Count Files to be deleted */
369 pm_strcpy(query, cnt_DelCand);
370 Dmsg1(100, "select sql=%s\n", query);
372 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
373 bsendmsg(ua, "%s", db_strerror(ua->db));
374 Dmsg0(050, "Count failed\n");
378 if (cnt.count == 0) {
380 bsendmsg(ua, _("No Jobs found to prune.\n"));
385 if (cnt.count < MAX_DEL_LIST_LEN) {
386 del.max_ids = cnt.count + 1;
388 del.max_ids = MAX_DEL_LIST_LEN;
390 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
391 del.PurgedFiles = (char *)malloc(del.max_ids);
394 edit_int64(cr.ClientId, ed2);
397 Mmsg(query, select_backup_del, ed1, ed1, ed2);
400 Mmsg(query, select_restore_del, ed1, ed1, ed2);
403 Mmsg(query, select_verify_del, ed1, ed1, ed2);
406 Mmsg(query, select_admin_del, ed1, ed1, ed2);
409 if (!db_sql_query(ua->db, query, job_delete_handler, (void *)&del)) {
410 bsendmsg(ua, "%s", db_strerror(ua->db));
414 * OK, now we have the list of JobId's to be pruned, first check
415 * if the Files have been purged, if not, purge (delete) them.
416 * Then delete the Job entry, and finally and JobMedia records.
418 for (i=0; i < del.num_ids; i++) {
419 if (!del.PurgedFiles[i]) {
420 purge_files_from_job(ua, del.JobId[i]);
422 purge_job_from_catalog(ua, del.JobId[i]);
424 bsendmsg(ua, _("Pruned %d %s for client %s from catalog.\n"), del.num_ids,
425 del.num_ids==1?_("Job"):_("Jobs"), client->hdr.name);
428 drop_temp_tables(ua);
433 if (del.PurgedFiles) {
434 free(del.PurgedFiles);
436 free_pool_memory(query);
441 * Prune a given Volume
443 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
445 POOLMEM *query = get_pool_memory(PM_MESSAGE);
446 struct s_count_ctx cnt;
447 struct s_file_del_ctx del;
454 if (mr->Enabled == 2) {
455 return false; /* Cannot prune archived volumes */
459 memset(&jr, 0, sizeof(jr));
460 memset(&del, 0, sizeof(del));
463 * Find out how many Jobs remain on this Volume by
464 * counting the JobMedia records.
467 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
468 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
469 bsendmsg(ua, "%s", db_strerror(ua->db));
470 Dmsg0(050, "Count failed\n");
474 if (cnt.count == 0) {
475 /* Don't mark appendable volume as purged */
476 if (strcmp(mr->VolStatus, "Append") == 0 && verbose) {
477 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Prune not needed.\n"),
482 /* If volume not already purged, do so */
483 if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
484 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
487 ok = mark_media_purged(ua, mr);
491 if (cnt.count < MAX_DEL_LIST_LEN) {
492 del.max_ids = cnt.count + 1;
494 del.max_ids = MAX_DEL_LIST_LEN;
498 * Now get a list of JobIds for Jobs written to this Volume
499 * Could optimize here by adding JobTDate > (now - period).
501 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
502 Mmsg(query, sel_JobMedia, edit_int64(mr->MediaId, ed1));
503 if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
505 bsendmsg(ua, "%s", db_strerror(ua->db));
507 Dmsg0(050, "Count failed\n");
511 /* Use Volume Retention to prune Jobs and their Files */
512 period = mr->VolRetention;
513 now = (utime_t)time(NULL);
515 Dmsg3(200, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
518 for (i=0; i < del.num_ids; i++) {
519 jr.JobId = del.JobId[i];
520 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
523 Dmsg2(200, "Looking at %s JobTdate=%d\n", jr.Job, (int)jr.JobTDate);
524 if (jr.JobTDate >= (now - period)) {
527 purge_files_from_job(ua, del.JobId[i]);
528 purge_job_from_catalog(ua, del.JobId[i]);
534 if (ua->verbose && del.num_del != 0) {
535 bsendmsg(ua, _("Pruned %d %s on Volume \"%s\" from catalog.\n"), del.num_del,
536 del.num_del == 1 ? "Job" : "Jobs", mr->VolumeName);
540 * Find out how many Jobs remain on this Volume by
541 * counting the JobMedia records.
544 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
545 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
546 bsendmsg(ua, "%s", db_strerror(ua->db));
547 Dmsg0(050, "Count failed\n");
550 if (cnt.count == 0) {
551 Dmsg0(200, "Volume is purged.\n");
552 ok = mark_media_purged(ua, mr);
557 free_pool_memory(query);