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 Jmsg(ua->jcr, M_INFO, 0, _("Begin pruning Jobs.\n"));
270 /* Select Jobs -- for counting */
271 edit_int64(now - period, ed1);
272 Mmsg(query, count_select_job, ed1, edit_int64(cr.ClientId, ed2));
273 Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now,
274 (uint32_t)period, query.c_str());
276 if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
277 ua->error_msg("%s", db_strerror(ua->db));
278 Dmsg0(050, "Count failed\n");
282 if (cnt.count == 0) {
284 ua->warning_msg(_("No Files found to prune.\n"));
289 if (cnt.count < MAX_DEL_LIST_LEN) {
290 del.max_ids = cnt.count + 1;
292 del.max_ids = MAX_DEL_LIST_LEN;
296 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
298 /* Now process same set but making a delete list */
299 Mmsg(query, select_job, edit_int64(now - period, ed1),
300 edit_int64(cr.ClientId, ed2));
301 db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del);
303 purge_files_from_job_list(ua, del);
305 edit_uint64_with_commas(del.num_del, ed1);
306 ua->info_msg(_("Pruned Files from %s Jobs for client %s from catalog.\n"),
307 ed1, client->name());
311 /* ***FIXME*** remove this for production */
312 sm_check(__FILE__, __LINE__, true);
315 /* ***FIXME*** remove this for production */
316 sm_check(__FILE__, __LINE__, true);
322 static void drop_temp_tables(UAContext *ua)
325 for (i=0; drop_deltabs[i]; i++) {
326 db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
330 static bool create_temp_tables(UAContext *ua)
332 /* Create temp tables and indicies */
333 if (!db_sql_query(ua->db, create_deltabs[db_type], NULL, (void *)NULL)) {
334 ua->error_msg("%s", db_strerror(ua->db));
335 Dmsg0(050, "create DelTables table failed\n");
338 if (!db_sql_query(ua->db, create_delindex, NULL, (void *)NULL)) {
339 ua->error_msg("%s", db_strerror(ua->db));
340 Dmsg0(050, "create DelInx1 index failed\n");
349 * Pruning Jobs is a bit more complicated than purging Files
350 * because we delete Job records only if there is a more current
351 * backup of the FileSet. Otherwise, we keep the Job record.
352 * In other words, we never delete the only Job record that
353 * contains a current backup of a FileSet. This prevents the
354 * Volume from being recycled and destroying a current backup.
356 * For Verify Jobs, we do not delete the last InitCatalog.
358 * For Restore Jobs there are no restrictions.
360 int prune_jobs(UAContext *ua, CLIENT *client, POOL *pool, int JobType)
363 POOL_MEM query(PM_MESSAGE);
366 char ed1[50], ed2[50];
369 memset(&cr, 0, sizeof(cr));
370 memset(&del, 0, sizeof(del));
372 bstrncpy(cr.Name, client->name(), sizeof(cr.Name));
373 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
378 if (pool && pool->JobRetention > 0) {
379 period = pool->JobRetention;
381 period = client->JobRetention;
383 now = (utime_t)time(NULL);
385 /* Drop any previous temporary tables still there */
386 drop_temp_tables(ua);
388 /* Create temp tables and indicies */
389 if (!create_temp_tables(ua)) {
395 * Select all files that are older than the JobRetention period
396 * and stuff them into the "DeletionCandidates" table.
398 edit_utime(now-period, ed1, sizeof(ed1));
399 Jmsg(ua->jcr, M_INFO, 0, _("Begin pruning Jobs older than %s secs.\n"), ed1);
400 edit_int64(now - period, ed1);
401 Mmsg(query, insert_delcand, (char)JobType, ed1,
402 edit_int64(cr.ClientId, ed2));
403 if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) {
405 ua->error_msg("%s", db_strerror(ua->db));
407 Dmsg0(050, "insert delcand failed\n");
412 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
413 del.PurgedFiles = (char *)malloc(del.max_ids);
416 edit_int64(cr.ClientId, ed2);
419 Mmsg(query, select_backup_del, ed1, ed2);
422 Mmsg(query, select_restore_del, ed1, ed2);
425 Mmsg(query, select_verify_del, ed1, ed2);
428 Mmsg(query, select_admin_del, ed1, ed2);
431 Mmsg(query, select_copy_del, ed1, ed2);
434 Mmsg(query, select_migrate_del, ed1, ed2);
438 Dmsg1(150, "Query=%s\n", query.c_str());
439 if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
440 ua->error_msg("%s", db_strerror(ua->db));
443 purge_job_list_from_catalog(ua, del);
445 if (del.num_del > 0) {
446 ua->info_msg(_("Pruned %d %s for client %s from catalog.\n"), del.num_del,
447 del.num_del==1?_("Job"):_("Jobs"), client->name());
448 } else if (ua->verbose) {
449 ua->info_msg(_("No Jobs found to prune.\n"));
453 drop_temp_tables(ua);
458 if (del.PurgedFiles) {
459 free(del.PurgedFiles);
465 * Prune a given Volume
467 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
469 POOL_MEM query(PM_MESSAGE);
474 if (mr->Enabled == 2) {
475 return false; /* Cannot prune archived volumes */
478 memset(&del, 0, sizeof(del));
480 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
484 /* Prune only Volumes with status "Full", or "Used" */
485 if (strcmp(mr->VolStatus, "Full") == 0 ||
486 strcmp(mr->VolStatus, "Used") == 0) {
487 Dmsg2(050, "get prune list MediaId=%d Volume %s\n", (int)mr->MediaId, mr->VolumeName);
488 count = get_prune_list_for_volume(ua, mr, &del);
489 Dmsg1(050, "Num pruned = %d\n", count);
491 purge_job_list_from_catalog(ua, del);
493 ok = is_volume_purged(ua, mr);
504 * Get prune list for a volume
506 int get_prune_list_for_volume(UAContext *ua, MEDIA_DBR *mr, del_ctx *del)
508 POOL_MEM query(PM_MESSAGE);
511 char ed1[50], ed2[50];
513 if (mr->Enabled == 2) {
514 return 0; /* cannot prune Archived volumes */
518 * Now add to the list of JobIds for Jobs written to this Volume
520 edit_int64(mr->MediaId, ed1);
521 period = mr->VolRetention;
522 now = (utime_t)time(NULL);
523 edit_int64(now-period, ed2);
524 Mmsg(query, sel_JobMedia, ed1, ed2);
525 Dmsg3(250, "Now=%d period=%d now-period=%s\n", (int)now, (int)period,
528 Dmsg1(050, "Query=%s\n", query.c_str());
529 if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)del)) {
531 ua->error_msg("%s", db_strerror(ua->db));
533 Dmsg0(050, "Count failed\n");
536 count = exclude_running_jobs_from_list(del);
543 * We have a list of jobs to prune or purge. If any of them is
544 * currently running, we set its JobId to zero which effectively
547 * Returns the number of jobs that can be prunned or purged.
550 int exclude_running_jobs_from_list(del_ctx *prune_list)
557 /* Do not prune any job currently running */
558 for (i=0; i < prune_list->num_ids; i++) {
561 if (jcr->JobId == prune_list->JobId[i]) {
562 Dmsg2(050, "skip running job JobId[%d]=%d\n", i, (int)prune_list->JobId[i]);
563 prune_list->JobId[i] = 0;
570 continue; /* don't increment count */
572 Dmsg2(050, "accept JobId[%d]=%d\n", i, (int)prune_list->JobId[i]);