3 * Bacula Director -- User Agent Database prune Command
4 * Applies retention periods
6 * Kern Sibbald, February MMII
11 Bacula® - The Network Backup Solution
13 Copyright (C) 2002-2006 Free Software Foundation Europe e.V.
15 The main author of Bacula is Kern Sibbald, with contributions from
16 many others, a complete list can be found in the file AUTHORS.
17 This program is Free Software; you can redistribute it and/or
18 modify it under the terms of version two of the GNU General Public
19 License as published by the Free Software Foundation plus additions
20 that are listed in the file LICENSE.
22 This program is distributed in the hope that it will be useful, but
23 WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 General Public License for more details.
27 You should have received a copy of the GNU General Public License
28 along with this program; if not, write to the Free Software
29 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
32 Bacula® is a registered trademark of John Walker.
33 The licensor of Bacula is the Free Software Foundation Europe
34 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
35 Switzerland, email:ftf@fsfeurope.org.
41 /* Imported functions */
43 /* Forward referenced functions */
46 #define MAX_DEL_LIST_LEN 2000000
48 /* In memory list of JobIds */
49 struct s_file_del_ctx {
51 int num_ids; /* ids stored */
52 int max_ids; /* size of array */
53 int num_del; /* number deleted */
54 int tot_ids; /* total to process */
57 struct s_job_del_ctx {
58 JobId_t *JobId; /* array of JobIds */
59 char *PurgedFiles; /* Array of PurgedFile flags */
60 int num_ids; /* ids stored */
61 int max_ids; /* size of array */
62 int num_del; /* number deleted */
63 int tot_ids; /* total to process */
72 * Called here to count entries to be deleted
74 static int count_handler(void *ctx, int num_fields, char **row)
76 struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
79 cnt->count = str_to_int64(row[0]);
88 * Called here to count the number of Jobs to be pruned
90 static int file_count_handler(void *ctx, int num_fields, char **row)
92 struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
99 * Called here to make in memory list of JobIds to be
100 * deleted and the associated PurgedFiles flag.
101 * The in memory list will then be transversed
102 * to issue the SQL DELETE commands. Note, the list
103 * is allowed to get to MAX_DEL_LIST_LEN to limit the
104 * maximum malloc'ed memory.
106 static int job_delete_handler(void *ctx, int num_fields, char **row)
108 struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
110 if (del->num_ids == MAX_DEL_LIST_LEN) {
113 if (del->num_ids == del->max_ids) {
114 del->max_ids = (del->max_ids * 3) / 2;
115 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
116 del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
118 del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
119 del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[1]);
123 static int file_delete_handler(void *ctx, int num_fields, char **row)
125 struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
127 if (del->num_ids == MAX_DEL_LIST_LEN) {
130 if (del->num_ids == del->max_ids) {
131 del->max_ids = (del->max_ids * 3) / 2;
132 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
135 del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
140 * Prune records from database
142 * prune files (from) client=xxx
143 * prune jobs (from) client=xxx
146 int prunecmd(UAContext *ua, const char *cmd)
153 static const char *keywords[] = {
159 if (!open_client_db(ua)) {
163 /* First search args */
164 kw = find_arg_keyword(ua, keywords);
165 if (kw < 0 || kw > 2) {
166 /* no args, so ask user */
167 kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);
171 case 0: /* prune files */
172 client = get_client_resource(ua);
173 if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
176 prune_files(ua, client);
178 case 1: /* prune jobs */
179 client = get_client_resource(ua);
180 if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
183 /* ****FIXME**** allow user to select JobType */
184 prune_jobs(ua, client, JT_BACKUP);
186 case 2: /* prune volume */
187 if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
190 if (mr.Enabled == 2) {
191 bsendmsg(ua, _("Cannot prune Volume \"%s\" because it is archived.\n"),
194 if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
197 prune_volume(ua, &mr);
207 * Prune File records from the database. For any Job which
208 * is older than the retention period, we unconditionally delete
209 * all File records for that Job. This is simple enough that no
210 * temporary tables are needed. We simply make an in memory list of
211 * the JobIds meeting the prune conditions, then delete all File records
212 * pointing to each of those JobIds.
214 * This routine assumes you want the pruning to be done. All checking
215 * must be done before calling this routine.
217 int prune_files(UAContext *ua, CLIENT *client)
219 struct s_file_del_ctx del;
220 POOLMEM *query = get_pool_memory(PM_MESSAGE);
224 char ed1[50], ed2[50];
227 memset(&cr, 0, sizeof(cr));
228 memset(&del, 0, sizeof(del));
229 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
230 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
235 period = client->FileRetention;
236 now = (utime_t)time(NULL);
238 /* Select Jobs -- for counting */
239 Mmsg(query, select_job, edit_uint64(now - period, ed1),
240 edit_int64(cr.ClientId, ed2));
241 Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, (uint32_t)period, query);
242 if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
244 bsendmsg(ua, "%s", db_strerror(ua->db));
246 Dmsg0(050, "Count failed\n");
250 if (del.tot_ids == 0) {
252 bsendmsg(ua, _("No Files found to prune.\n"));
257 if (del.tot_ids < MAX_DEL_LIST_LEN) {
258 del.max_ids = del.tot_ids + 1;
260 del.max_ids = MAX_DEL_LIST_LEN;
264 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
266 /* Now process same set but making a delete list */
267 db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
269 for (i=0; i < del.num_ids; i++) {
270 purge_files_from_job(ua, del.JobId[i]);
272 edit_uint64_with_commas(del.num_ids, ed1);
273 bsendmsg(ua, _("Pruned Files from %s Jobs for client %s from catalog.\n"),
274 ed1, client->hdr.name);
281 free_pool_memory(query);
286 static void drop_temp_tables(UAContext *ua)
289 for (i=0; drop_deltabs[i]; i++) {
290 db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
294 static bool create_temp_tables(UAContext *ua)
297 /* Create temp tables and indicies */
298 for (i=0; create_deltabs[i]; i++) {
299 if (!db_sql_query(ua->db, create_deltabs[i], NULL, (void *)NULL)) {
300 bsendmsg(ua, "%s", db_strerror(ua->db));
301 Dmsg0(050, "create DelTables table failed\n");
311 * Pruning Jobs is a bit more complicated than purging Files
312 * because we delete Job records only if there is a more current
313 * backup of the FileSet. Otherwise, we keep the Job record.
314 * In other words, we never delete the only Job record that
315 * contains a current backup of a FileSet. This prevents the
316 * Volume from being recycled and destroying a current backup.
318 * For Verify Jobs, we do not delete the last InitCatalog.
320 * For Restore Jobs there are no restrictions.
322 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
324 struct s_job_del_ctx del;
325 struct s_count_ctx cnt;
326 POOLMEM *query = get_pool_memory(PM_MESSAGE);
330 char ed1[50], ed2[50];
333 memset(&cr, 0, sizeof(cr));
334 memset(&del, 0, sizeof(del));
335 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
336 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
341 period = client->JobRetention;
342 now = (utime_t)time(NULL);
344 /* Drop any previous temporary tables still there */
345 drop_temp_tables(ua);
347 /* Create temp tables and indicies */
348 if (!create_temp_tables(ua)) {
353 * Select all files that are older than the JobRetention period
354 * and stuff them into the "DeletionCandidates" table.
356 edit_uint64(now - period, ed1);
357 Mmsg(query, insert_delcand, (char)JobType, ed1,
358 edit_int64(cr.ClientId, ed2));
359 if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
361 bsendmsg(ua, "%s", db_strerror(ua->db));
363 Dmsg0(050, "insert delcand failed\n");
367 /* Count Files to be deleted */
368 pm_strcpy(query, cnt_DelCand);
369 Dmsg1(100, "select sql=%s\n", query);
371 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
372 bsendmsg(ua, "%s", db_strerror(ua->db));
373 Dmsg0(050, "Count failed\n");
377 if (cnt.count == 0) {
379 bsendmsg(ua, _("No Jobs found to prune.\n"));
384 if (cnt.count < MAX_DEL_LIST_LEN) {
385 del.max_ids = cnt.count + 1;
387 del.max_ids = MAX_DEL_LIST_LEN;
389 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
390 del.PurgedFiles = (char *)malloc(del.max_ids);
393 edit_int64(cr.ClientId, ed2);
396 Mmsg(query, select_backup_del, ed1, ed2);
399 Mmsg(query, select_restore_del, ed1, ed2);
402 Mmsg(query, select_verify_del, ed1, ed2);
405 Mmsg(query, select_admin_del, ed1, ed2);
408 Mmsg(query, select_migrate_del, ed1, ed2);
411 if (!db_sql_query(ua->db, query, job_delete_handler, (void *)&del)) {
412 bsendmsg(ua, "%s", db_strerror(ua->db));
416 * OK, now we have the list of JobId's to be pruned, first check
417 * if the Files have been purged, if not, purge (delete) them.
418 * Then delete the Job entry, and finally and JobMedia records.
420 for (i=0; i < del.num_ids; i++) {
421 if (!del.PurgedFiles[i]) {
422 purge_files_from_job(ua, del.JobId[i]);
424 purge_job_from_catalog(ua, del.JobId[i]);
426 bsendmsg(ua, _("Pruned %d %s for client %s from catalog.\n"), del.num_ids,
427 del.num_ids==1?_("Job"):_("Jobs"), client->hdr.name);
430 drop_temp_tables(ua);
435 if (del.PurgedFiles) {
436 free(del.PurgedFiles);
438 free_pool_memory(query);
443 * Prune a given Volume
445 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
447 POOLMEM *query = get_pool_memory(PM_MESSAGE);
448 struct s_count_ctx cnt;
449 struct s_file_del_ctx del;
456 if (mr->Enabled == 2) {
457 return false; /* Cannot prune archived volumes */
461 memset(&jr, 0, sizeof(jr));
462 memset(&del, 0, sizeof(del));
465 * Find out how many Jobs remain on this Volume by
466 * counting the JobMedia records.
469 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
470 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
471 bsendmsg(ua, "%s", db_strerror(ua->db));
472 Dmsg0(050, "Count failed\n");
476 if (cnt.count == 0) {
477 /* Don't mark appendable volume as purged */
478 if (strcmp(mr->VolStatus, "Append") == 0 && verbose) {
479 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Prune not needed.\n"),
484 /* If volume not already purged, do so */
485 if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
486 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
489 ok = mark_media_purged(ua, mr);
493 if (cnt.count < MAX_DEL_LIST_LEN) {
494 del.max_ids = cnt.count + 1;
496 del.max_ids = MAX_DEL_LIST_LEN;
500 * Now get a list of JobIds for Jobs written to this Volume
501 * Could optimize here by adding JobTDate > (now - period).
503 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
504 Mmsg(query, sel_JobMedia, edit_int64(mr->MediaId, ed1));
505 if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
507 bsendmsg(ua, "%s", db_strerror(ua->db));
509 Dmsg0(050, "Count failed\n");
513 /* Use Volume Retention to prune Jobs and their Files */
514 period = mr->VolRetention;
515 now = (utime_t)time(NULL);
517 Dmsg3(200, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
520 for (i=0; i < del.num_ids; i++) {
521 jr.JobId = del.JobId[i];
522 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
525 Dmsg2(200, "Looking at %s JobTdate=%d\n", jr.Job, (int)jr.JobTDate);
526 if (jr.JobTDate >= (now - period)) {
529 purge_files_from_job(ua, del.JobId[i]);
530 purge_job_from_catalog(ua, del.JobId[i]);
536 if (ua->verbose && del.num_del != 0) {
537 bsendmsg(ua, _("Pruned %d %s on Volume \"%s\" from catalog.\n"), del.num_del,
538 del.num_del == 1 ? "Job" : "Jobs", mr->VolumeName);
542 * Find out how many Jobs remain on this Volume by
543 * counting the JobMedia records.
546 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
547 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
548 bsendmsg(ua, "%s", db_strerror(ua->db));
549 Dmsg0(050, "Count failed\n");
552 if (cnt.count == 0) {
553 Dmsg0(200, "Volume is purged.\n");
554 ok = mark_media_purged(ua, mr);
559 free_pool_memory(query);