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 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 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 * 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)
119 static const char *keywords[] = {
126 if (!open_client_db(ua)) {
130 /* First search args */
131 kw = find_arg_keyword(ua, keywords);
132 if (kw < 0 || kw > 3) {
133 /* no args, so ask user */
134 kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);
138 case 0: /* prune files */
139 client = get_client_resource(ua);
140 if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
143 prune_files(ua, client);
145 case 1: /* prune jobs */
146 client = get_client_resource(ua);
147 if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
150 /* ****FIXME**** allow user to select JobType */
151 prune_jobs(ua, client, JT_BACKUP);
153 case 2: /* prune volume */
154 if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
157 if (mr.Enabled == 2) {
158 ua->error_msg(_("Cannot prune Volume \"%s\" because it is archived.\n"),
162 if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
165 prune_volume(ua, &mr);
167 case 3: /* prune stats */
168 /* TODO: prune JobStat table */
178 * Prune File records from the database. For any Job which
179 * is older than the retention period, we unconditionally delete
180 * all File records for that Job. This is simple enough that no
181 * temporary tables are needed. We simply make an in memory list of
182 * the JobIds meeting the prune conditions, then delete all File records
183 * pointing to each of those JobIds.
185 * This routine assumes you want the pruning to be done. All checking
186 * must be done before calling this routine.
188 int prune_files(UAContext *ua, CLIENT *client)
191 struct s_count_ctx cnt;
192 POOL_MEM query(PM_MESSAGE);
195 char ed1[50], ed2[50];
198 memset(&cr, 0, sizeof(cr));
199 memset(&del, 0, sizeof(del));
200 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
201 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
206 period = client->FileRetention;
207 now = (utime_t)time(NULL);
209 /* Select Jobs -- for counting */
210 Mmsg(query, count_select_job, edit_uint64(now - period, ed1),
211 edit_int64(cr.ClientId, ed2));
212 Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now,
213 (uint32_t)period, query.c_str());
215 if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
216 ua->error_msg("%s", db_strerror(ua->db));
217 Dmsg0(050, "Count failed\n");
221 if (cnt.count == 0) {
223 ua->warning_msg(_("No Files found to prune.\n"));
228 if (cnt.count < MAX_DEL_LIST_LEN) {
229 del.max_ids = cnt.count + 1;
231 del.max_ids = MAX_DEL_LIST_LEN;
235 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
237 /* Now process same set but making a delete list */
238 Mmsg(query, select_job, edit_uint64(now - period, ed1),
239 edit_int64(cr.ClientId, ed2));
240 db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del);
242 purge_files_from_job_list(ua, del);
244 edit_uint64_with_commas(del.num_del, ed1);
245 ua->info_msg(_("Pruned Files from %s Jobs for client %s from catalog.\n"),
246 ed1, client->name());
257 static void drop_temp_tables(UAContext *ua)
260 for (i=0; drop_deltabs[i]; i++) {
261 db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
265 static bool create_temp_tables(UAContext *ua)
267 /* Create temp tables and indicies */
268 if (!db_sql_query(ua->db, create_deltabs[db_type], NULL, (void *)NULL)) {
269 ua->error_msg("%s", db_strerror(ua->db));
270 Dmsg0(050, "create DelTables table failed\n");
273 if (!db_sql_query(ua->db, create_delindex, NULL, (void *)NULL)) {
274 ua->error_msg("%s", db_strerror(ua->db));
275 Dmsg0(050, "create DelInx1 index failed\n");
284 * Pruning Jobs is a bit more complicated than purging Files
285 * because we delete Job records only if there is a more current
286 * backup of the FileSet. Otherwise, we keep the Job record.
287 * In other words, we never delete the only Job record that
288 * contains a current backup of a FileSet. This prevents the
289 * Volume from being recycled and destroying a current backup.
291 * For Verify Jobs, we do not delete the last InitCatalog.
293 * For Restore Jobs there are no restrictions.
295 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
298 POOL_MEM query(PM_MESSAGE);
301 char ed1[50], ed2[50];
304 memset(&cr, 0, sizeof(cr));
305 memset(&del, 0, sizeof(del));
307 bstrncpy(cr.Name, client->name(), sizeof(cr.Name));
308 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
313 period = client->JobRetention;
314 now = (utime_t)time(NULL);
316 /* Drop any previous temporary tables still there */
317 drop_temp_tables(ua);
319 /* Create temp tables and indicies */
320 if (!create_temp_tables(ua)) {
325 * Select all files that are older than the JobRetention period
326 * and stuff them into the "DeletionCandidates" table.
328 edit_uint64(now - period, ed1);
329 Mmsg(query, insert_delcand, (char)JobType, ed1,
330 edit_int64(cr.ClientId, ed2));
331 if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) {
333 ua->error_msg("%s", db_strerror(ua->db));
335 Dmsg0(050, "insert delcand failed\n");
340 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
341 del.PurgedFiles = (char *)malloc(del.max_ids);
344 edit_int64(cr.ClientId, ed2);
347 Mmsg(query, select_backup_del, ed1, ed2);
350 Mmsg(query, select_restore_del, ed1, ed2);
353 Mmsg(query, select_verify_del, ed1, ed2);
356 Mmsg(query, select_admin_del, ed1, ed2);
359 Mmsg(query, select_copy_del, ed1, ed2);
362 Mmsg(query, select_migrate_del, ed1, ed2);
366 Dmsg1(150, "Query=%s\n", query.c_str());
367 if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
368 ua->error_msg("%s", db_strerror(ua->db));
371 purge_job_list_from_catalog(ua, del);
373 if (del.num_del > 0) {
374 ua->info_msg(_("Pruned %d %s for client %s from catalog.\n"), del.num_del,
375 del.num_del==1?_("Job"):_("Jobs"), client->name());
376 } else if (ua->verbose) {
377 ua->info_msg(_("No Jobs found to prune.\n"));
381 drop_temp_tables(ua);
386 if (del.PurgedFiles) {
387 free(del.PurgedFiles);
393 * Prune a given Volume
395 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
397 POOL_MEM query(PM_MESSAGE);
402 if (mr->Enabled == 2) {
403 return false; /* Cannot prune archived volumes */
406 memset(&del, 0, sizeof(del));
408 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
412 /* Prune only Volumes with status "Full", or "Used" */
413 if (strcmp(mr->VolStatus, "Full") == 0 ||
414 strcmp(mr->VolStatus, "Used") == 0) {
415 Dmsg2(050, "get prune list MediaId=%d Volume %s\n", (int)mr->MediaId, mr->VolumeName);
416 count = get_prune_list_for_volume(ua, mr, &del);
417 Dmsg1(050, "Num pruned = %d\n", count);
419 purge_job_list_from_catalog(ua, del);
421 ok = is_volume_purged(ua, mr);
432 * Get prune list for a volume
434 int get_prune_list_for_volume(UAContext *ua, MEDIA_DBR *mr, del_ctx *del)
436 POOL_MEM query(PM_MESSAGE);
440 char ed1[50], ed2[50];
442 if (mr->Enabled == 2) {
443 return 0; /* cannot prune Archived volumes */
449 * Now add to the list of JobIds for Jobs written to this Volume
451 edit_int64(mr->MediaId, ed1);
452 period = mr->VolRetention;
453 now = (utime_t)time(NULL);
454 edit_uint64(now-period, ed2);
455 Mmsg(query, sel_JobMedia, ed1, ed2);
456 Dmsg3(250, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
459 Dmsg1(050, "Query=%s\n", query.c_str());
460 if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)del)) {
462 ua->error_msg("%s", db_strerror(ua->db));
464 Dmsg0(050, "Count failed\n");
468 for (i=0; i < del->num_ids; i++) {
469 if (ua->jcr->JobId == del->JobId[i]) {
470 Dmsg2(150, "skip same job JobId[%d]=%d\n", i, (int)del->JobId[i]);
474 Dmsg2(150, "accept JobId[%d]=%d\n", i, (int)del->JobId[i]);