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 1000000
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;
49 extern char *del_JobMedia;
50 extern char *cnt_JobMedia;
51 extern char *sel_JobMedia;
54 /* In memory list of JobIds */
55 struct s_file_del_ctx {
57 int num_ids; /* ids stored */
58 int max_ids; /* size of array */
59 int num_del; /* number deleted */
60 int tot_ids; /* total to process */
63 struct s_job_del_ctx {
64 JobId_t *JobId; /* array of JobIds */
65 char *PurgedFiles; /* Array of PurgedFile flags */
66 int num_ids; /* ids stored */
67 int max_ids; /* size of array */
68 int num_del; /* number deleted */
69 int tot_ids; /* total to process */
78 * Called here to count entries to be deleted
80 static int count_handler(void *ctx, int num_fields, char **row)
82 struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
85 cnt->count = str_to_int64(row[0]);
94 * Called here to count the number of Jobs to be pruned
96 static int file_count_handler(void *ctx, int num_fields, char **row)
98 struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
105 * Called here to make in memory list of JobIds to be
106 * deleted and the associated PurgedFiles flag.
107 * The in memory list will then be transversed
108 * to issue the SQL DELETE commands. Note, the list
109 * is allowed to get to MAX_DEL_LIST_LEN to limit the
110 * maximum malloc'ed memory.
112 static int job_delete_handler(void *ctx, int num_fields, char **row)
114 struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
116 if (del->num_ids == MAX_DEL_LIST_LEN) {
119 if (del->num_ids == del->max_ids) {
120 del->max_ids = (del->max_ids * 3) / 2;
121 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
122 del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
124 del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
125 del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[0]);
129 static int file_delete_handler(void *ctx, int num_fields, char **row)
131 struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
133 if (del->num_ids == MAX_DEL_LIST_LEN) {
136 if (del->num_ids == del->max_ids) {
137 del->max_ids = (del->max_ids * 3) / 2;
138 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
141 del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
146 * Prune records from database
148 * prune files (from) client=xxx
149 * prune jobs (from) client=xxx
152 int prunecmd(UAContext *ua, const char *cmd)
159 static const char *keywords[] = {
169 /* First search args */
170 kw = find_arg_keyword(ua, keywords);
171 if (kw < 0 || kw > 2) {
172 /* no args, so ask user */
173 kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);
177 case 0: /* prune files */
178 client = get_client_resource(ua);
179 if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
182 prune_files(ua, client);
184 case 1: /* prune jobs */
185 client = get_client_resource(ua);
186 if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
189 /* ****FIXME**** allow user to select JobType */
190 prune_jobs(ua, client, JT_BACKUP);
192 case 2: /* prune volume */
193 if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
196 if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
199 prune_volume(ua, &mr);
209 * Prune File records from the database. For any Job which
210 * is older than the retention period, we unconditionally delete
211 * all File records for that Job. This is simple enough that no
212 * temporary tables are needed. We simply make an in memory list of
213 * the JobIds meeting the prune conditions, then delete all File records
214 * pointing to each of those JobIds.
216 * This routine assumes you want the pruning to be done. All checking
217 * must be done before calling this routine.
219 int prune_files(UAContext *ua, CLIENT *client)
221 struct s_file_del_ctx del;
222 POOLMEM *query = get_pool_memory(PM_MESSAGE);
226 char ed1[50], ed2[50];
229 memset(&cr, 0, sizeof(cr));
230 memset(&del, 0, sizeof(del));
231 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
232 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
237 period = client->FileRetention;
238 now = (utime_t)time(NULL);
240 /* Select Jobs -- for counting */
241 Mmsg(query, select_job, edit_uint64(now - period, ed1),
242 edit_int64(cr.ClientId, ed2));
243 Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, (uint32_t)period, query);
244 if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
246 bsendmsg(ua, "%s", db_strerror(ua->db));
248 Dmsg0(050, "Count failed\n");
252 if (del.tot_ids == 0) {
254 bsendmsg(ua, _("No Files found to prune.\n"));
259 if (del.tot_ids < MAX_DEL_LIST_LEN) {
260 del.max_ids = del.tot_ids + 1;
262 del.max_ids = MAX_DEL_LIST_LEN;
266 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
268 /* Now process same set but making a delete list */
269 db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
271 for (i=0; i < del.num_ids; i++) {
272 Mmsg(query, del_File, edit_int64(del.JobId[i], ed1));
273 Dmsg1(000, "Delete Files JobId=%s\n", ed1);
274 db_sql_query(ua->db, query, NULL, (void *)NULL);
276 * Now mark Job as having files purged. This is necessary to
277 * avoid having too many Jobs to process in future prunings. If
278 * we don't do this, the number of JobId's in our in memory list
279 * could grow very large.
281 Mmsg(query, upd_Purged, edit_int64(del.JobId[i], ed1));
282 db_sql_query(ua->db, query, NULL, (void *)NULL);
283 Dmsg1(000, "Update Purged sql=%s\n", query);
285 edit_uint64_with_commas(del.num_ids, ed1);
286 bsendmsg(ua, _("Pruned Files from %s Jobs for client %s from catalog.\n"),
287 ed1, client->hdr.name);
294 free_pool_memory(query);
299 static void drop_temp_tables(UAContext *ua)
302 for (i=0; drop_deltabs[i]; i++) {
303 db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
307 static bool create_temp_tables(UAContext *ua)
310 /* Create temp tables and indicies */
311 for (i=0; create_deltabs[i]; i++) {
312 if (!db_sql_query(ua->db, create_deltabs[i], NULL, (void *)NULL)) {
313 bsendmsg(ua, "%s", db_strerror(ua->db));
314 Dmsg0(050, "create DelTables table failed\n");
324 * Purging Jobs is a bit more complicated than purging Files
325 * because we delete Job records only if there is a more current
326 * backup of the FileSet. Otherwise, we keep the Job record.
327 * In other words, we never delete the only Job record that
328 * contains a current backup of a FileSet. This prevents the
329 * Volume from being recycled and destroying a current backup.
331 * For Verify Jobs, we do not delete the last InitCatalog.
333 * For Restore Jobs there are no restrictions.
335 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
337 struct s_job_del_ctx del;
338 struct s_count_ctx cnt;
339 POOLMEM *query = (char *)get_pool_memory(PM_MESSAGE);
343 char ed1[50], ed2[50];
346 memset(&cr, 0, sizeof(cr));
347 memset(&del, 0, sizeof(del));
348 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
349 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
354 period = client->JobRetention;
355 now = (utime_t)time(NULL);
357 /* Drop any previous temporary tables still there */
358 drop_temp_tables(ua);
360 /* Create temp tables and indicies */
361 if (!create_temp_tables(ua)) {
366 * Select all files that are older than the JobRetention period
367 * and stuff them into the "DeletionCandidates" table.
369 edit_uint64(now - period, ed1);
370 Mmsg(query, insert_delcand, (char)JobType, ed1,
371 edit_int64(cr.ClientId, ed2));
372 if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
374 bsendmsg(ua, "%s", db_strerror(ua->db));
376 Dmsg0(050, "insert delcand failed\n");
380 /* Count Files to be deleted */
381 pm_strcpy(query, cnt_DelCand);
382 Dmsg1(100, "select sql=%s\n", query);
384 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
385 bsendmsg(ua, "%s", db_strerror(ua->db));
386 Dmsg0(050, "Count failed\n");
390 if (cnt.count == 0) {
392 bsendmsg(ua, _("No Jobs found to prune.\n"));
397 if (cnt.count < MAX_DEL_LIST_LEN) {
398 del.max_ids = cnt.count + 1;
400 del.max_ids = MAX_DEL_LIST_LEN;
402 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
403 del.PurgedFiles = (char *)malloc(del.max_ids);
406 edit_int64(cr.ClientId, ed2);
409 Mmsg(query, select_backup_del, ed1, ed1, ed2);
412 Mmsg(query, select_restore_del, ed1, ed1, ed2);
415 Mmsg(query, select_verify_del, ed1, ed1, ed2);
418 Mmsg(query, select_admin_del, ed1, ed1, ed2);
421 if (!db_sql_query(ua->db, query, job_delete_handler, (void *)&del)) {
422 bsendmsg(ua, "%s", db_strerror(ua->db));
426 * OK, now we have the list of JobId's to be pruned, first check
427 * if the Files have been purged, if not, purge (delete) them.
428 * Then delete the Job entry, and finally and JobMedia records.
430 for (i=0; i < del.num_ids; i++) {
431 edit_int64(del.JobId[i], ed1);
432 Dmsg1(050, "Delete JobId=%s\n", ed1);
433 if (!del.PurgedFiles[i]) {
434 Mmsg(query, del_File, ed1);
435 if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
436 bsendmsg(ua, "%s", db_strerror(ua->db));
438 Dmsg1(050, "Del sql=%s\n", query);
441 Mmsg(query, del_Job, ed1);
442 if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
443 bsendmsg(ua, "%s", db_strerror(ua->db));
445 Dmsg1(050, "Del sql=%s\n", query);
447 Mmsg(query, del_JobMedia, ed1);
448 if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
449 bsendmsg(ua, "%s", db_strerror(ua->db));
451 Dmsg1(050, "Del sql=%s\n", query);
453 bsendmsg(ua, _("Pruned %d %s for client %s from catalog.\n"), del.num_ids,
454 del.num_ids==1?_("Job"):_("Jobs"), client->hdr.name);
457 drop_temp_tables(ua);
462 if (del.PurgedFiles) {
463 free(del.PurgedFiles);
465 free_pool_memory(query);
470 * Prune a given Volume
472 int prune_volume(UAContext *ua, MEDIA_DBR *mr)
474 POOLMEM *query = (char *)get_pool_memory(PM_MESSAGE);
475 struct s_count_ctx cnt;
476 struct s_file_del_ctx del;
483 memset(&jr, 0, sizeof(jr));
484 memset(&del, 0, sizeof(del));
487 * Find out how many Jobs remain on this Volume by
488 * counting the JobMedia records.
491 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
492 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
493 bsendmsg(ua, "%s", db_strerror(ua->db));
494 Dmsg0(050, "Count failed\n");
498 if (cnt.count == 0) {
499 /* Don't mark appendable volume as purged */
500 if (strcmp(mr->VolStatus, "Append") == 0 && verbose) {
501 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Prune not needed.\n"),
506 /* If volume not already purged, do so */
507 if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
508 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
511 stat = mark_media_purged(ua, mr);
515 if (cnt.count < MAX_DEL_LIST_LEN) {
516 del.max_ids = cnt.count + 1;
518 del.max_ids = MAX_DEL_LIST_LEN;
522 * Now get a list of JobIds for Jobs written to this Volume
523 * Could optimize here by adding JobTDate > (now - period).
525 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
526 Mmsg(query, sel_JobMedia, edit_int64(mr->MediaId, ed1));
527 if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
529 bsendmsg(ua, "%s", db_strerror(ua->db));
531 Dmsg0(050, "Count failed\n");
535 /* Use Volume Retention to prune Jobs and their Files */
536 period = mr->VolRetention;
537 now = (utime_t)time(NULL);
539 Dmsg3(200, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
542 for (i=0; i < del.num_ids; i++) {
543 jr.JobId = del.JobId[i];
544 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
547 Dmsg2(200, "Looking at %s JobTdate=%d\n", jr.Job, (int)jr.JobTDate);
548 if (jr.JobTDate >= (now - period)) {
551 edit_int64(del.JobId[i], ed1);
552 Dmsg2(200, "Delete JobId=%s Job=%s\n", ed1, jr.Job);
553 Mmsg(query, del_File, ed1);
554 db_sql_query(ua->db, query, NULL, (void *)NULL);
555 Mmsg(query, del_Job, ed1);
556 db_sql_query(ua->db, query, NULL, (void *)NULL);
557 Mmsg(query, del_JobMedia, ed1);
558 db_sql_query(ua->db, query, NULL, (void *)NULL);
559 Dmsg1(050, "Del sql=%s\n", query);
565 if (ua->verbose && del.num_del != 0) {
566 bsendmsg(ua, _("Pruned %d %s on Volume \"%s\" from catalog.\n"), del.num_del,
567 del.num_del == 1 ? "Job" : "Jobs", mr->VolumeName);
570 /* If purged, mark it so */
571 if (del.num_ids == del.num_del) {
572 Dmsg0(200, "Volume is purged.\n");
573 stat = mark_media_purged(ua, mr);
578 free_pool_memory(query);