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 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 /* Don't prune current job */
271 if (ua->jcr->JobId != del.JobId[i]) {
272 purge_files_from_job(ua, del.JobId[i]);
276 edit_uint64_with_commas(del.num_del, ed1);
277 bsendmsg(ua, _("Pruned Files from %s Jobs for client %s from catalog.\n"),
278 ed1, client->name());
285 free_pool_memory(query);
290 static void drop_temp_tables(UAContext *ua)
293 for (i=0; drop_deltabs[i]; i++) {
294 db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
298 static bool create_temp_tables(UAContext *ua)
301 /* Create temp tables and indicies */
302 for (i=0; create_deltabs[i]; i++) {
303 if (!db_sql_query(ua->db, create_deltabs[i], NULL, (void *)NULL)) {
304 bsendmsg(ua, "%s", db_strerror(ua->db));
305 Dmsg0(050, "create DelTables table failed\n");
315 * Pruning Jobs is a bit more complicated than purging Files
316 * because we delete Job records only if there is a more current
317 * backup of the FileSet. Otherwise, we keep the Job record.
318 * In other words, we never delete the only Job record that
319 * contains a current backup of a FileSet. This prevents the
320 * Volume from being recycled and destroying a current backup.
322 * For Verify Jobs, we do not delete the last InitCatalog.
324 * For Restore Jobs there are no restrictions.
326 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
328 struct s_job_del_ctx del;
329 struct s_count_ctx cnt;
330 POOLMEM *query = get_pool_memory(PM_MESSAGE);
334 char ed1[50], ed2[50];
337 memset(&cr, 0, sizeof(cr));
338 memset(&del, 0, sizeof(del));
339 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
340 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
345 period = client->JobRetention;
346 now = (utime_t)time(NULL);
348 /* Drop any previous temporary tables still there */
349 drop_temp_tables(ua);
351 /* Create temp tables and indicies */
352 if (!create_temp_tables(ua)) {
357 * Select all files that are older than the JobRetention period
358 * and stuff them into the "DeletionCandidates" table.
360 edit_uint64(now - period, ed1);
361 Mmsg(query, insert_delcand, (char)JobType, ed1,
362 edit_int64(cr.ClientId, ed2));
363 if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
365 bsendmsg(ua, "%s", db_strerror(ua->db));
367 Dmsg0(050, "insert delcand failed\n");
371 /* Count Files to be deleted */
372 pm_strcpy(query, cnt_DelCand);
373 Dmsg1(100, "select sql=%s\n", query);
375 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
376 bsendmsg(ua, "%s", db_strerror(ua->db));
377 Dmsg0(050, "Count failed\n");
381 if (cnt.count == 0) {
383 bsendmsg(ua, _("No Jobs found to prune.\n"));
388 if (cnt.count < MAX_DEL_LIST_LEN) {
389 del.max_ids = cnt.count + 1;
391 del.max_ids = MAX_DEL_LIST_LEN;
393 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
394 del.PurgedFiles = (char *)malloc(del.max_ids);
397 edit_int64(cr.ClientId, ed2);
400 Mmsg(query, select_backup_del, ed1, ed2);
403 Mmsg(query, select_restore_del, ed1, ed2);
406 Mmsg(query, select_verify_del, ed1, ed2);
409 Mmsg(query, select_admin_del, ed1, ed2);
412 Mmsg(query, select_migrate_del, ed1, ed2);
415 if (!db_sql_query(ua->db, query, job_delete_handler, (void *)&del)) {
416 bsendmsg(ua, "%s", db_strerror(ua->db));
420 * OK, now we have the list of JobId's to be pruned, first check
421 * if the Files have been purged, if not, purge (delete) them.
422 * Then delete the Job entry, and finally and JobMedia records.
424 for (i=0; i < del.num_ids; i++) {
425 /* Don't prune current job */
426 if (ua->jcr->JobId != del.JobId[i]) {
427 if (!del.PurgedFiles[i]) {
428 purge_files_from_job(ua, del.JobId[i]);
430 purge_job_from_catalog(ua, del.JobId[i]);
434 bsendmsg(ua, _("Pruned %d %s for client %s from catalog.\n"), del.num_del,
435 del.num_del==1?_("Job"):_("Jobs"), client->name());
438 drop_temp_tables(ua);
443 if (del.PurgedFiles) {
444 free(del.PurgedFiles);
446 free_pool_memory(query);
451 * Prune a given Volume
453 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
455 POOLMEM *query = get_pool_memory(PM_MESSAGE);
456 struct s_count_ctx cnt;
457 struct s_file_del_ctx del;
464 if (mr->Enabled == 2) {
465 return false; /* Cannot prune archived volumes */
469 memset(&jr, 0, sizeof(jr));
470 memset(&del, 0, sizeof(del));
473 * Find out how many Jobs remain on this Volume by
474 * counting the JobMedia records.
477 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
478 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
479 bsendmsg(ua, "%s", db_strerror(ua->db));
480 Dmsg0(050, "Count failed\n");
484 if (cnt.count == 0) {
485 /* Don't mark appendable volume as purged */
486 if (strcmp(mr->VolStatus, "Append") == 0 && verbose) {
487 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Prune not needed.\n"),
492 /* If volume not already purged, do so */
493 if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
494 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
497 ok = mark_media_purged(ua, mr);
501 if (cnt.count < MAX_DEL_LIST_LEN) {
502 del.max_ids = cnt.count + 1;
504 del.max_ids = MAX_DEL_LIST_LEN;
508 * Now get a list of JobIds for Jobs written to this Volume
509 * Could optimize here by adding JobTDate > (now - period).
511 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
512 Mmsg(query, sel_JobMedia, edit_int64(mr->MediaId, ed1));
513 if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
515 bsendmsg(ua, "%s", db_strerror(ua->db));
517 Dmsg0(050, "Count failed\n");
521 /* Use Volume Retention to prune Jobs and their Files */
522 period = mr->VolRetention;
523 now = (utime_t)time(NULL);
525 Dmsg3(200, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
528 for (i=0; i < del.num_ids; i++) {
529 jr.JobId = del.JobId[i];
530 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
533 Dmsg2(200, "Looking at %s JobTdate=%d\n", jr.Job, (int)jr.JobTDate);
534 if (jr.JobTDate >= (now - period) || ua->jcr->JobId == del.JobId[i]) {
537 purge_files_from_job(ua, del.JobId[i]);
538 purge_job_from_catalog(ua, del.JobId[i]);
544 if (ua->verbose && del.num_del != 0) {
545 bsendmsg(ua, _("Pruned %d %s on Volume \"%s\" from catalog.\n"), del.num_del,
546 del.num_del == 1 ? "Job" : "Jobs", mr->VolumeName);
550 * Find out how many Jobs remain on this Volume by
551 * counting the JobMedia records.
554 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
555 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
556 bsendmsg(ua, "%s", db_strerror(ua->db));
557 Dmsg0(050, "Count failed\n");
560 if (cnt.count == 0) {
561 Dmsg0(200, "Volume is purged.\n");
562 ok = mark_media_purged(ua, mr);
567 free_pool_memory(query);