2 Bacula® - The Network Backup Solution
4 Copyright (C) 2002-2008 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 and included
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 Kern Sibbald.
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 * Called here to count entries to be deleted
48 int del_count_handler(void *ctx, int num_fields, char **row)
50 struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
53 cnt->count = str_to_int64(row[0]);
62 * Called here to make in memory list of JobIds to be
63 * deleted and the associated PurgedFiles flag.
64 * The in memory list will then be transversed
65 * to issue the SQL DELETE commands. Note, the list
66 * is allowed to get to MAX_DEL_LIST_LEN to limit the
67 * maximum malloc'ed memory.
69 int job_delete_handler(void *ctx, int num_fields, char **row)
71 struct del_ctx *del = (struct del_ctx *)ctx;
73 if (del->num_ids == MAX_DEL_LIST_LEN) {
76 if (del->num_ids == del->max_ids) {
77 del->max_ids = (del->max_ids * 3) / 2;
78 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
79 del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
81 del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
82 // Dmsg2(60, "row=%d val=%d\n", del->num_ids, del->JobId[del->num_ids]);
83 del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[1]);
87 int file_delete_handler(void *ctx, int num_fields, char **row)
89 struct del_ctx *del = (struct del_ctx *)ctx;
91 if (del->num_ids == MAX_DEL_LIST_LEN) {
94 if (del->num_ids == del->max_ids) {
95 del->max_ids = (del->max_ids * 3) / 2;
96 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
99 del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
100 // Dmsg2(150, "row=%d val=%d\n", del->num_ids-1, del->JobId[del->num_ids-1]);
105 * Prune records from database
107 * prune files (from) client=xxx
108 * prune jobs (from) client=xxx
112 int prunecmd(UAContext *ua, const char *cmd)
121 static const char *keywords[] = {
128 if (!open_client_db(ua)) {
132 /* First search args */
133 kw = find_arg_keyword(ua, keywords);
134 if (kw < 0 || kw > 3) {
135 /* no args, so ask user */
136 kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);
140 case 0: /* prune files */
141 client = get_client_resource(ua);
142 if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
145 prune_files(ua, client);
147 case 1: /* prune jobs */
148 client = get_client_resource(ua);
149 if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
152 /* ****FIXME**** allow user to select JobType */
153 prune_jobs(ua, client, JT_BACKUP);
155 case 2: /* prune volume */
156 if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
159 if (mr.Enabled == 2) {
160 ua->error_msg(_("Cannot prune Volume \"%s\" because it is archived.\n"),
164 if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
167 prune_volume(ua, &mr);
169 case 3: /* prune stats */
170 dir = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
171 if (!dir->stats_retention) {
174 retention = dir->stats_retention;
175 if (!confirm_retention(ua, &retention, "Statistics")) {
178 prune_stats(ua, retention);
187 /* Prune Job stat records from the database.
190 int prune_stats(UAContext *ua, utime_t retention)
193 POOL_MEM query(PM_MESSAGE);
194 utime_t now = (utime_t)time(NULL);
197 Mmsg(query, "DELETE FROM JobHistory WHERE JobTDate < %s",
198 edit_int64(now - retention, ed1));
199 db_sql_query(ua->db, query.c_str(), NULL, NULL);
202 ua->info_msg(_("Pruned Jobs from JobHistory catalog.\n"));
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)
221 struct s_count_ctx cnt;
222 POOL_MEM query(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, count_select_job, edit_int64(now - period, ed1),
241 edit_int64(cr.ClientId, ed2));
242 Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now,
243 (uint32_t)period, query.c_str());
245 if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
246 ua->error_msg("%s", db_strerror(ua->db));
247 Dmsg0(050, "Count failed\n");
251 if (cnt.count == 0) {
253 ua->warning_msg(_("No Files found to prune.\n"));
258 if (cnt.count < MAX_DEL_LIST_LEN) {
259 del.max_ids = cnt.count + 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 Mmsg(query, select_job, edit_int64(now - period, ed1),
269 edit_int64(cr.ClientId, ed2));
270 db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del);
272 purge_files_from_job_list(ua, del);
274 edit_uint64_with_commas(del.num_del, ed1);
275 ua->info_msg(_("Pruned Files from %s Jobs for client %s from catalog.\n"),
276 ed1, client->name());
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)
297 /* Create temp tables and indicies */
298 if (!db_sql_query(ua->db, create_deltabs[db_type], NULL, (void *)NULL)) {
299 ua->error_msg("%s", db_strerror(ua->db));
300 Dmsg0(050, "create DelTables table failed\n");
303 if (!db_sql_query(ua->db, create_delindex, NULL, (void *)NULL)) {
304 ua->error_msg("%s", db_strerror(ua->db));
305 Dmsg0(050, "create DelInx1 index failed\n");
314 * Pruning Jobs is a bit more complicated than purging Files
315 * because we delete Job records only if there is a more current
316 * backup of the FileSet. Otherwise, we keep the Job record.
317 * In other words, we never delete the only Job record that
318 * contains a current backup of a FileSet. This prevents the
319 * Volume from being recycled and destroying a current backup.
321 * For Verify Jobs, we do not delete the last InitCatalog.
323 * For Restore Jobs there are no restrictions.
325 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
328 POOL_MEM query(PM_MESSAGE);
331 char ed1[50], ed2[50];
334 memset(&cr, 0, sizeof(cr));
335 memset(&del, 0, sizeof(del));
337 bstrncpy(cr.Name, client->name(), sizeof(cr.Name));
338 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
343 period = client->JobRetention;
344 now = (utime_t)time(NULL);
346 /* Drop any previous temporary tables still there */
347 drop_temp_tables(ua);
349 /* Create temp tables and indicies */
350 if (!create_temp_tables(ua)) {
355 * Select all files that are older than the JobRetention period
356 * and stuff them into the "DeletionCandidates" table.
358 edit_int64(now - period, ed1);
359 Mmsg(query, insert_delcand, (char)JobType, ed1,
360 edit_int64(cr.ClientId, ed2));
361 if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) {
363 ua->error_msg("%s", db_strerror(ua->db));
365 Dmsg0(050, "insert delcand failed\n");
370 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
371 del.PurgedFiles = (char *)malloc(del.max_ids);
374 edit_int64(cr.ClientId, ed2);
377 Mmsg(query, select_backup_del, ed1, ed2);
380 Mmsg(query, select_restore_del, ed1, ed2);
383 Mmsg(query, select_verify_del, ed1, ed2);
386 Mmsg(query, select_admin_del, ed1, ed2);
389 Mmsg(query, select_copy_del, ed1, ed2);
392 Mmsg(query, select_migrate_del, ed1, ed2);
396 Dmsg1(150, "Query=%s\n", query.c_str());
397 if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
398 ua->error_msg("%s", db_strerror(ua->db));
401 purge_job_list_from_catalog(ua, del);
403 if (del.num_del > 0) {
404 ua->info_msg(_("Pruned %d %s for client %s from catalog.\n"), del.num_del,
405 del.num_del==1?_("Job"):_("Jobs"), client->name());
406 } else if (ua->verbose) {
407 ua->info_msg(_("No Jobs found to prune.\n"));
411 drop_temp_tables(ua);
416 if (del.PurgedFiles) {
417 free(del.PurgedFiles);
423 * Prune a given Volume
425 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
427 POOL_MEM query(PM_MESSAGE);
432 if (mr->Enabled == 2) {
433 return false; /* Cannot prune archived volumes */
436 memset(&del, 0, sizeof(del));
438 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
442 /* Prune only Volumes with status "Full", or "Used" */
443 if (strcmp(mr->VolStatus, "Full") == 0 ||
444 strcmp(mr->VolStatus, "Used") == 0) {
445 Dmsg2(050, "get prune list MediaId=%d Volume %s\n", (int)mr->MediaId, mr->VolumeName);
446 count = get_prune_list_for_volume(ua, mr, &del);
447 Dmsg1(050, "Num pruned = %d\n", count);
449 purge_job_list_from_catalog(ua, del);
451 ok = is_volume_purged(ua, mr);
462 * Get prune list for a volume
464 int get_prune_list_for_volume(UAContext *ua, MEDIA_DBR *mr, del_ctx *del)
466 POOL_MEM query(PM_MESSAGE);
470 char ed1[50], ed2[50];
474 if (mr->Enabled == 2) {
475 return 0; /* cannot prune Archived volumes */
481 * Now add to the list of JobIds for Jobs written to this Volume
483 edit_int64(mr->MediaId, ed1);
484 period = mr->VolRetention;
485 now = (utime_t)time(NULL);
486 edit_int64(now-period, ed2);
487 Mmsg(query, sel_JobMedia, ed1, ed2);
488 Dmsg3(250, "Now=%d period=%d now-period=%s\n", (int)now, (int)period,
491 Dmsg1(050, "Query=%s\n", query.c_str());
492 if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)del)) {
494 ua->error_msg("%s", db_strerror(ua->db));
496 Dmsg0(050, "Count failed\n");
500 /* Do not prune any job currently running */
501 for (i=0; i < del->num_ids; i++) {
504 if (jcr->JobId == del->JobId[i]) {
505 Dmsg2(150, "skip same job JobId[%d]=%d\n", i, (int)del->JobId[i]);
515 Dmsg2(150, "accept JobId[%d]=%d\n", i, (int)del->JobId[i]);