2 Bacula® - The Network Backup Solution
4 Copyright (C) 2002-2007 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from
7 many others, a complete list can be found in the file AUTHORS.
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version two of the GNU General Public
10 License as published by the Free Software Foundation plus additions
11 that are listed in the file LICENSE.
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 Bacula® is a registered trademark of John Walker.
24 The licensor of Bacula is the Free Software Foundation Europe
25 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26 Switzerland, email:ftf@fsfeurope.org.
30 * Bacula Director -- User Agent Database prune Command
31 * Applies retention periods
33 * Kern Sibbald, February MMII
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 make in memory list of JobIds to be
89 * deleted and the associated PurgedFiles flag.
90 * The in memory list will then be transversed
91 * to issue the SQL DELETE commands. Note, the list
92 * is allowed to get to MAX_DEL_LIST_LEN to limit the
93 * maximum malloc'ed memory.
95 static int job_delete_handler(void *ctx, int num_fields, char **row)
97 struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
99 if (del->num_ids == MAX_DEL_LIST_LEN) {
102 if (del->num_ids == del->max_ids) {
103 del->max_ids = (del->max_ids * 3) / 2;
104 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
105 del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
107 del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
108 del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[1]);
112 static int file_delete_handler(void *ctx, int num_fields, char **row)
114 struct s_file_del_ctx *del = (struct s_file_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) *
124 del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
129 * Prune records from database
131 * prune files (from) client=xxx
132 * prune jobs (from) client=xxx
135 int prunecmd(UAContext *ua, const char *cmd)
142 static const char *keywords[] = {
148 if (!open_client_db(ua)) {
152 /* First search args */
153 kw = find_arg_keyword(ua, keywords);
154 if (kw < 0 || kw > 2) {
155 /* no args, so ask user */
156 kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);
160 case 0: /* prune files */
161 client = get_client_resource(ua);
162 if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
165 prune_files(ua, client);
167 case 1: /* prune jobs */
168 client = get_client_resource(ua);
169 if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
172 /* ****FIXME**** allow user to select JobType */
173 prune_jobs(ua, client, JT_BACKUP);
175 case 2: /* prune volume */
176 if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
179 if (mr.Enabled == 2) {
180 bsendmsg(ua, _("Cannot prune Volume \"%s\" because it is archived.\n"),
183 if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
186 prune_volume(ua, &mr);
196 * Prune File records from the database. For any Job which
197 * is older than the retention period, we unconditionally delete
198 * all File records for that Job. This is simple enough that no
199 * temporary tables are needed. We simply make an in memory list of
200 * the JobIds meeting the prune conditions, then delete all File records
201 * pointing to each of those JobIds.
203 * This routine assumes you want the pruning to be done. All checking
204 * must be done before calling this routine.
206 int prune_files(UAContext *ua, CLIENT *client)
208 struct s_file_del_ctx del;
209 struct s_count_ctx cnt;
210 POOLMEM *query = get_pool_memory(PM_MESSAGE);
214 char ed1[50], ed2[50];
217 memset(&cr, 0, sizeof(cr));
218 memset(&del, 0, sizeof(del));
219 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
220 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
225 period = client->FileRetention;
226 now = (utime_t)time(NULL);
228 /* Select Jobs -- for counting */
229 Mmsg(query, count_select_job, edit_uint64(now - period, ed1),
230 edit_int64(cr.ClientId, ed2));
231 Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, (uint32_t)period, query);
233 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
234 bsendmsg(ua, "%s", db_strerror(ua->db));
235 Dmsg0(050, "Count failed\n");
239 if (cnt.count == 0) {
241 bsendmsg(ua, _("No Files found to prune.\n"));
246 if (cnt.count < MAX_DEL_LIST_LEN) {
247 del.max_ids = cnt.count + 1;
249 del.max_ids = MAX_DEL_LIST_LEN;
253 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
255 /* Now process same set but making a delete list */
256 Mmsg(query, select_job, edit_uint64(now - period, ed1),
257 edit_int64(cr.ClientId, ed2));
258 db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
260 for (i=0; i < del.num_ids; i++) {
261 /* Don't prune current job */
262 if (ua->jcr->JobId != del.JobId[i]) {
263 purge_files_from_job(ua, del.JobId[i]);
267 edit_uint64_with_commas(del.num_del, ed1);
268 bsendmsg(ua, _("Pruned Files from %s Jobs for client %s from catalog.\n"),
269 ed1, client->name());
276 free_pool_memory(query);
281 static void drop_temp_tables(UAContext *ua)
284 for (i=0; drop_deltabs[i]; i++) {
285 db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
289 static bool create_temp_tables(UAContext *ua)
292 /* Create temp tables and indicies */
293 for (i=0; create_deltabs[i]; i++) {
294 if (!db_sql_query(ua->db, create_deltabs[i], NULL, (void *)NULL)) {
295 bsendmsg(ua, "%s", db_strerror(ua->db));
296 Dmsg0(050, "create DelTables table failed\n");
306 * Pruning Jobs is a bit more complicated than purging Files
307 * because we delete Job records only if there is a more current
308 * backup of the FileSet. Otherwise, we keep the Job record.
309 * In other words, we never delete the only Job record that
310 * contains a current backup of a FileSet. This prevents the
311 * Volume from being recycled and destroying a current backup.
313 * For Verify Jobs, we do not delete the last InitCatalog.
315 * For Restore Jobs there are no restrictions.
317 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
319 struct s_job_del_ctx del;
320 struct s_count_ctx cnt;
321 POOLMEM *query = get_pool_memory(PM_MESSAGE);
325 char ed1[50], ed2[50];
328 memset(&cr, 0, sizeof(cr));
329 memset(&del, 0, sizeof(del));
330 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
331 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
336 period = client->JobRetention;
337 now = (utime_t)time(NULL);
339 /* Drop any previous temporary tables still there */
340 drop_temp_tables(ua);
342 /* Create temp tables and indicies */
343 if (!create_temp_tables(ua)) {
348 * Select all files that are older than the JobRetention period
349 * and stuff them into the "DeletionCandidates" table.
351 edit_uint64(now - period, ed1);
352 Mmsg(query, insert_delcand, (char)JobType, ed1,
353 edit_int64(cr.ClientId, ed2));
354 if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
356 bsendmsg(ua, "%s", db_strerror(ua->db));
358 Dmsg0(050, "insert delcand failed\n");
362 /* Count Files to be deleted */
363 pm_strcpy(query, cnt_DelCand);
364 Dmsg1(100, "select sql=%s\n", query);
366 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
367 bsendmsg(ua, "%s", db_strerror(ua->db));
368 Dmsg0(050, "Count failed\n");
372 if (cnt.count == 0) {
374 bsendmsg(ua, _("No Jobs found to prune.\n"));
379 if (cnt.count < MAX_DEL_LIST_LEN) {
380 del.max_ids = cnt.count + 1;
382 del.max_ids = MAX_DEL_LIST_LEN;
384 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
385 del.PurgedFiles = (char *)malloc(del.max_ids);
388 edit_int64(cr.ClientId, ed2);
391 Mmsg(query, select_backup_del, ed1, ed2);
394 Mmsg(query, select_restore_del, ed1, ed2);
397 Mmsg(query, select_verify_del, ed1, ed2);
400 Mmsg(query, select_admin_del, ed1, ed2);
403 Mmsg(query, select_migrate_del, ed1, ed2);
406 if (!db_sql_query(ua->db, query, job_delete_handler, (void *)&del)) {
407 bsendmsg(ua, "%s", db_strerror(ua->db));
411 * OK, now we have the list of JobId's to be pruned, send them
412 * off to be deleted batched 1000 at a time.
415 for (i=0; del.num_ids; ) {
416 for (int j=0; j<1000 && del.num_ids; j++) {
418 if (ua->jcr->JobId == del.JobId[i]) {
421 pm_strcat(query, ",");
422 pm_strcpy(query, edit_int64(del.JobId[i++], ed1));
425 purge_jobs_from_catalog(ua, query);
427 bsendmsg(ua, _("Pruned %d %s for client %s from catalog.\n"), del.num_del,
428 del.num_del==1?_("Job"):_("Jobs"), client->name());
431 drop_temp_tables(ua);
436 if (del.PurgedFiles) {
437 free(del.PurgedFiles);
439 free_pool_memory(query);
444 * Prune a given Volume
446 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
448 POOLMEM *query = get_pool_memory(PM_MESSAGE);
449 struct s_count_ctx cnt;
450 struct s_file_del_ctx del;
457 if (mr->Enabled == 2) {
458 return false; /* Cannot prune archived volumes */
462 memset(&jr, 0, sizeof(jr));
463 memset(&del, 0, sizeof(del));
466 * Find out how many Jobs remain on this Volume by
467 * counting the JobMedia records.
470 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
471 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
472 bsendmsg(ua, "%s", db_strerror(ua->db));
473 Dmsg0(050, "Count failed\n");
477 if (cnt.count == 0) {
478 /* Don't mark appendable volume as purged */
479 if (strcmp(mr->VolStatus, "Append") == 0 && verbose) {
480 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Prune not needed.\n"),
485 /* If volume not already purged, do so */
486 if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
487 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
490 ok = mark_media_purged(ua, mr);
494 if (cnt.count < MAX_DEL_LIST_LEN) {
495 del.max_ids = cnt.count + 1;
497 del.max_ids = MAX_DEL_LIST_LEN;
501 * Now get a list of JobIds for Jobs written to this Volume
502 * Could optimize here by adding JobTDate > (now - period).
504 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
505 Mmsg(query, sel_JobMedia, edit_int64(mr->MediaId, ed1));
506 if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
508 bsendmsg(ua, "%s", db_strerror(ua->db));
510 Dmsg0(050, "Count failed\n");
514 /* Use Volume Retention to prune Jobs and their Files */
515 period = mr->VolRetention;
516 now = (utime_t)time(NULL);
518 Dmsg3(200, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
521 for (i=0; i < del.num_ids; i++) {
522 jr.JobId = del.JobId[i];
523 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
526 Dmsg2(200, "Looking at %s JobTdate=%d\n", jr.Job, (int)jr.JobTDate);
527 if (jr.JobTDate >= (now - period) || ua->jcr->JobId == del.JobId[i]) {
530 purge_files_from_job(ua, del.JobId[i]);
531 purge_job_from_catalog(ua, del.JobId[i]);
537 if (ua->verbose && del.num_del != 0) {
538 bsendmsg(ua, _("Pruned %d %s on Volume \"%s\" from catalog.\n"), del.num_del,
539 del.num_del == 1 ? "Job" : "Jobs", mr->VolumeName);
543 * Find out how many Jobs remain on this Volume by
544 * counting the JobMedia records.
547 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
548 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
549 bsendmsg(ua, "%s", db_strerror(ua->db));
550 Dmsg0(050, "Count failed\n");
553 if (cnt.count == 0) {
554 Dmsg0(200, "Volume is purged.\n");
555 ok = mark_media_purged(ua, mr);
560 free_pool_memory(query);