2 Bacula® - The Network Backup Solution
4 Copyright (C) 2002-2009 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
40 /* Imported functions */
42 /* Forward referenced functions */
45 * Called here to count entries to be deleted
47 int del_count_handler(void *ctx, int num_fields, char **row)
49 struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
52 cnt->count = str_to_int64(row[0]);
61 * Called here to make in memory list of JobIds to be
62 * deleted and the associated PurgedFiles flag.
63 * The in memory list will then be transversed
64 * to issue the SQL DELETE commands. Note, the list
65 * is allowed to get to MAX_DEL_LIST_LEN to limit the
66 * maximum malloc'ed memory.
68 int job_delete_handler(void *ctx, int num_fields, char **row)
70 struct del_ctx *del = (struct del_ctx *)ctx;
72 if (del->num_ids == MAX_DEL_LIST_LEN) {
75 if (del->num_ids == del->max_ids) {
76 del->max_ids = (del->max_ids * 3) / 2;
77 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
78 del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
80 del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
81 // Dmsg2(60, "row=%d val=%d\n", del->num_ids, del->JobId[del->num_ids]);
82 del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[1]);
86 int file_delete_handler(void *ctx, int num_fields, char **row)
88 struct del_ctx *del = (struct del_ctx *)ctx;
90 if (del->num_ids == MAX_DEL_LIST_LEN) {
93 if (del->num_ids == del->max_ids) {
94 del->max_ids = (del->max_ids * 3) / 2;
95 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
98 del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
99 /* **FIXME*** remove in production */
100 sm_check(__FILE__, __LINE__, true);
101 // Dmsg2(150, "row=%d val=%d\n", del->num_ids-1, del->JobId[del->num_ids-1]);
106 * Prune records from database
108 * prune files (from) client=xxx [pool=yyy]
109 * prune jobs (from) client=xxx [pool=yyy]
113 int prunecmd(UAContext *ua, const char *cmd)
123 static const char *keywords[] = {
130 if (!open_client_db(ua)) {
134 /* First search args */
135 kw = find_arg_keyword(ua, keywords);
136 if (kw < 0 || kw > 3) {
137 /* no args, so ask user */
138 kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);
142 case 0: /* prune files */
143 client = get_client_resource(ua);
144 if (find_arg_with_value(ua, "pool") >= 0) {
145 pool = get_pool_resource(ua);
149 /* Pool File Retention takes precedence over client File Retention */
150 if (pool && pool->FileRetention > 0) {
151 if (!confirm_retention(ua, &pool->FileRetention, "File")) {
154 } else if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
157 prune_files(ua, client, pool);
159 case 1: /* prune jobs */
160 client = get_client_resource(ua);
161 if (find_arg_with_value(ua, "pool") >= 0) {
162 pool = get_pool_resource(ua);
166 /* Pool Job Retention takes precedence over client Job Retention */
167 if (pool && pool->JobRetention > 0) {
168 if (!confirm_retention(ua, &pool->JobRetention, "Job")) {
171 } else if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
174 /* ****FIXME**** allow user to select JobType */
175 prune_jobs(ua, client, pool, JT_BACKUP);
177 case 2: /* prune volume */
178 if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
181 if (mr.Enabled == 2) {
182 ua->error_msg(_("Cannot prune Volume \"%s\" because it is archived.\n"),
186 if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
189 prune_volume(ua, &mr);
191 case 3: /* prune stats */
192 dir = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
193 if (!dir->stats_retention) {
196 retention = dir->stats_retention;
197 if (!confirm_retention(ua, &retention, "Statistics")) {
200 prune_stats(ua, retention);
209 /* Prune Job stat records from the database.
212 int prune_stats(UAContext *ua, utime_t retention)
215 POOL_MEM query(PM_MESSAGE);
216 utime_t now = (utime_t)time(NULL);
219 Mmsg(query, "DELETE FROM JobHisto WHERE JobTDate < %s",
220 edit_int64(now - retention, ed1));
221 db_sql_query(ua->db, query.c_str(), NULL, NULL);
224 ua->info_msg(_("Pruned Jobs from JobHisto catalog.\n"));
230 * Prune File records from the database. For any Job which
231 * is older than the retention period, we unconditionally delete
232 * all File records for that Job. This is simple enough that no
233 * temporary tables are needed. We simply make an in memory list of
234 * the JobIds meeting the prune conditions, then delete all File records
235 * pointing to each of those JobIds.
237 * This routine assumes you want the pruning to be done. All checking
238 * must be done before calling this routine.
240 * Note: pool can possibly be NULL.
242 int prune_files(UAContext *ua, CLIENT *client, POOL *pool)
245 struct s_count_ctx cnt;
246 POOL_MEM query(PM_MESSAGE);
249 char ed1[50], ed2[50];
252 memset(&cr, 0, sizeof(cr));
253 memset(&del, 0, sizeof(del));
254 bstrncpy(cr.Name, client->name(), sizeof(cr.Name));
255 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
260 if (pool && pool->FileRetention > 0) {
261 period = pool->FileRetention;
263 period = client->FileRetention;
265 now = (utime_t)time(NULL);
267 edit_utime(now-period, ed1, sizeof(ed1));
268 Jmsg(ua->jcr, M_INFO, 0, _("Begin pruning Jobs older than %s secs.\n"), ed1);
269 /* Select Jobs -- for counting */
270 edit_int64(now - period, ed1);
271 Mmsg(query, count_select_job, ed1, edit_int64(cr.ClientId, ed2));
272 Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now,
273 (uint32_t)period, query.c_str());
275 if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
276 ua->error_msg("%s", db_strerror(ua->db));
277 Dmsg0(050, "Count failed\n");
281 if (cnt.count == 0) {
283 ua->warning_msg(_("No Files found to prune.\n"));
288 if (cnt.count < MAX_DEL_LIST_LEN) {
289 del.max_ids = cnt.count + 1;
291 del.max_ids = MAX_DEL_LIST_LEN;
295 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
297 /* Now process same set but making a delete list */
298 Mmsg(query, select_job, edit_int64(now - period, ed1),
299 edit_int64(cr.ClientId, ed2));
300 db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del);
302 purge_files_from_job_list(ua, del);
304 edit_uint64_with_commas(del.num_del, ed1);
305 ua->info_msg(_("Pruned Files from %s Jobs for client %s from catalog.\n"),
306 ed1, client->name());
310 /* ***FIXME*** remove this for production */
311 sm_check(__FILE__, __LINE__, true);
314 /* ***FIXME*** remove this for production */
315 sm_check(__FILE__, __LINE__, true);
321 static void drop_temp_tables(UAContext *ua)
324 for (i=0; drop_deltabs[i]; i++) {
325 db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
329 static bool create_temp_tables(UAContext *ua)
331 /* Create temp tables and indicies */
332 if (!db_sql_query(ua->db, create_deltabs[db_type], NULL, (void *)NULL)) {
333 ua->error_msg("%s", db_strerror(ua->db));
334 Dmsg0(050, "create DelTables table failed\n");
337 if (!db_sql_query(ua->db, create_delindex, NULL, (void *)NULL)) {
338 ua->error_msg("%s", db_strerror(ua->db));
339 Dmsg0(050, "create DelInx1 index failed\n");
348 * Pruning Jobs is a bit more complicated than purging Files
349 * because we delete Job records only if there is a more current
350 * backup of the FileSet. Otherwise, we keep the Job record.
351 * In other words, we never delete the only Job record that
352 * contains a current backup of a FileSet. This prevents the
353 * Volume from being recycled and destroying a current backup.
355 * For Verify Jobs, we do not delete the last InitCatalog.
357 * For Restore Jobs there are no restrictions.
359 int prune_jobs(UAContext *ua, CLIENT *client, POOL *pool, int JobType)
362 POOL_MEM query(PM_MESSAGE);
365 char ed1[50], ed2[50];
368 memset(&cr, 0, sizeof(cr));
369 memset(&del, 0, sizeof(del));
371 bstrncpy(cr.Name, client->name(), sizeof(cr.Name));
372 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
377 if (pool && pool->JobRetention > 0) {
378 period = pool->JobRetention;
380 period = client->JobRetention;
382 now = (utime_t)time(NULL);
384 /* Drop any previous temporary tables still there */
385 drop_temp_tables(ua);
387 /* Create temp tables and indicies */
388 if (!create_temp_tables(ua)) {
394 * Select all files that are older than the JobRetention period
395 * and stuff them into the "DeletionCandidates" table.
397 edit_utime(now-period, ed1, sizeof(ed1));
398 Jmsg(ua->jcr, M_INFO, 0, _("Begin pruning Jobs older than %s secs.\n"), ed1);
399 edit_int64(now - period, ed1);
400 Mmsg(query, insert_delcand, (char)JobType, ed1,
401 edit_int64(cr.ClientId, ed2));
402 if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) {
404 ua->error_msg("%s", db_strerror(ua->db));
406 Dmsg0(050, "insert delcand failed\n");
411 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
412 del.PurgedFiles = (char *)malloc(del.max_ids);
415 edit_int64(cr.ClientId, ed2);
418 Mmsg(query, select_backup_del, ed1, ed2);
421 Mmsg(query, select_restore_del, ed1, ed2);
424 Mmsg(query, select_verify_del, ed1, ed2);
427 Mmsg(query, select_admin_del, ed1, ed2);
430 Mmsg(query, select_copy_del, ed1, ed2);
433 Mmsg(query, select_migrate_del, ed1, ed2);
437 Dmsg1(150, "Query=%s\n", query.c_str());
438 if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
439 ua->error_msg("%s", db_strerror(ua->db));
442 purge_job_list_from_catalog(ua, del);
444 if (del.num_del > 0) {
445 ua->info_msg(_("Pruned %d %s for client %s from catalog.\n"), del.num_del,
446 del.num_del==1?_("Job"):_("Jobs"), client->name());
447 } else if (ua->verbose) {
448 ua->info_msg(_("No Jobs found to prune.\n"));
452 drop_temp_tables(ua);
457 if (del.PurgedFiles) {
458 free(del.PurgedFiles);
464 * Prune a given Volume
466 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
468 POOL_MEM query(PM_MESSAGE);
473 if (mr->Enabled == 2) {
474 return false; /* Cannot prune archived volumes */
477 memset(&del, 0, sizeof(del));
479 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
483 /* Prune only Volumes with status "Full", or "Used" */
484 if (strcmp(mr->VolStatus, "Full") == 0 ||
485 strcmp(mr->VolStatus, "Used") == 0) {
486 Dmsg2(050, "get prune list MediaId=%d Volume %s\n", (int)mr->MediaId, mr->VolumeName);
487 count = get_prune_list_for_volume(ua, mr, &del);
488 Dmsg1(050, "Num pruned = %d\n", count);
490 purge_job_list_from_catalog(ua, del);
492 ok = is_volume_purged(ua, mr);
503 * Get prune list for a volume
505 int get_prune_list_for_volume(UAContext *ua, MEDIA_DBR *mr, del_ctx *del)
507 POOL_MEM query(PM_MESSAGE);
510 char ed1[50], ed2[50];
512 if (mr->Enabled == 2) {
513 return 0; /* cannot prune Archived volumes */
517 * Now add to the list of JobIds for Jobs written to this Volume
519 edit_int64(mr->MediaId, ed1);
520 period = mr->VolRetention;
521 now = (utime_t)time(NULL);
522 edit_int64(now-period, ed2);
523 Mmsg(query, sel_JobMedia, ed1, ed2);
524 Dmsg3(250, "Now=%d period=%d now-period=%s\n", (int)now, (int)period,
527 Dmsg1(050, "Query=%s\n", query.c_str());
528 if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)del)) {
530 ua->error_msg("%s", db_strerror(ua->db));
532 Dmsg0(050, "Count failed\n");
535 count = exclude_running_jobs_from_list(del);
542 * We have a list of jobs to prune or purge. If any of them is
543 * currently running, we set its JobId to zero which effectively
546 * Returns the number of jobs that can be prunned or purged.
549 int exclude_running_jobs_from_list(del_ctx *prune_list)
556 /* Do not prune any job currently running */
557 for (i=0; i < prune_list->num_ids; i++) {
560 if (jcr->JobId == prune_list->JobId[i]) {
561 Dmsg2(050, "skip running job JobId[%d]=%d\n", i, (int)prune_list->JobId[i]);
562 prune_list->JobId[i] = 0;
569 continue; /* don't increment count */
571 Dmsg2(050, "accept JobId[%d]=%d\n", i, (int)prune_list->JobId[i]);