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 * 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
111 int prunecmd(UAContext *ua, const char *cmd)
118 static const char *keywords[] = {
124 if (!open_client_db(ua)) {
128 /* First search args */
129 kw = find_arg_keyword(ua, keywords);
130 if (kw < 0 || kw > 2) {
131 /* no args, so ask user */
132 kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);
136 case 0: /* prune files */
137 client = get_client_resource(ua);
138 if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
141 prune_files(ua, client);
143 case 1: /* prune jobs */
144 client = get_client_resource(ua);
145 if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
148 /* ****FIXME**** allow user to select JobType */
149 prune_jobs(ua, client, JT_BACKUP);
151 case 2: /* prune volume */
152 if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
155 if (mr.Enabled == 2) {
156 ua->error_msg(_("Cannot prune Volume \"%s\" because it is archived.\n"),
160 if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
163 prune_volume(ua, &mr);
173 * Prune File records from the database. For any Job which
174 * is older than the retention period, we unconditionally delete
175 * all File records for that Job. This is simple enough that no
176 * temporary tables are needed. We simply make an in memory list of
177 * the JobIds meeting the prune conditions, then delete all File records
178 * pointing to each of those JobIds.
180 * This routine assumes you want the pruning to be done. All checking
181 * must be done before calling this routine.
183 int prune_files(UAContext *ua, CLIENT *client)
186 struct s_count_ctx cnt;
187 POOL_MEM query(PM_MESSAGE);
190 char ed1[50], ed2[50];
193 memset(&cr, 0, sizeof(cr));
194 memset(&del, 0, sizeof(del));
195 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
196 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
201 period = client->FileRetention;
202 now = (utime_t)time(NULL);
204 /* Select Jobs -- for counting */
205 Mmsg(query, count_select_job, edit_uint64(now - period, ed1),
206 edit_int64(cr.ClientId, ed2));
207 Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now,
208 (uint32_t)period, query.c_str());
210 if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
211 ua->error_msg("%s", db_strerror(ua->db));
212 Dmsg0(050, "Count failed\n");
216 if (cnt.count == 0) {
218 ua->warning_msg(_("No Files found to prune.\n"));
223 if (cnt.count < MAX_DEL_LIST_LEN) {
224 del.max_ids = cnt.count + 1;
226 del.max_ids = MAX_DEL_LIST_LEN;
230 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
232 /* Now process same set but making a delete list */
233 Mmsg(query, select_job, edit_uint64(now - period, ed1),
234 edit_int64(cr.ClientId, ed2));
235 db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del);
237 purge_files_from_job_list(ua, del);
239 edit_uint64_with_commas(del.num_del, ed1);
240 ua->info_msg(_("Pruned Files from %s Jobs for client %s from catalog.\n"),
241 ed1, client->name());
252 static void drop_temp_tables(UAContext *ua)
255 for (i=0; drop_deltabs[i]; i++) {
256 db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
260 static bool create_temp_tables(UAContext *ua)
263 /* Create temp tables and indicies */
264 for (i=0; create_deltabs[i]; i++) {
265 if (!db_sql_query(ua->db, create_deltabs[i], NULL, (void *)NULL)) {
266 ua->error_msg("%s", db_strerror(ua->db));
267 Dmsg0(050, "create DelTables table failed\n");
277 * Pruning Jobs is a bit more complicated than purging Files
278 * because we delete Job records only if there is a more current
279 * backup of the FileSet. Otherwise, we keep the Job record.
280 * In other words, we never delete the only Job record that
281 * contains a current backup of a FileSet. This prevents the
282 * Volume from being recycled and destroying a current backup.
284 * For Verify Jobs, we do not delete the last InitCatalog.
286 * For Restore Jobs there are no restrictions.
288 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
291 POOL_MEM query(PM_MESSAGE);
294 char ed1[50], ed2[50];
297 memset(&cr, 0, sizeof(cr));
298 memset(&del, 0, sizeof(del));
300 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
301 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
306 period = client->JobRetention;
307 now = (utime_t)time(NULL);
309 /* Drop any previous temporary tables still there */
310 drop_temp_tables(ua);
312 /* Create temp tables and indicies */
313 if (!create_temp_tables(ua)) {
318 * Select all files that are older than the JobRetention period
319 * and stuff them into the "DeletionCandidates" table.
321 edit_uint64(now - period, ed1);
322 Mmsg(query, insert_delcand, (char)JobType, ed1,
323 edit_int64(cr.ClientId, ed2));
324 if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) {
326 ua->error_msg("%s", db_strerror(ua->db));
328 Dmsg0(050, "insert delcand failed\n");
333 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
334 del.PurgedFiles = (char *)malloc(del.max_ids);
337 edit_int64(cr.ClientId, ed2);
340 Mmsg(query, select_backup_del, ed1, ed2);
343 Mmsg(query, select_restore_del, ed1, ed2);
346 Mmsg(query, select_verify_del, ed1, ed2);
349 Mmsg(query, select_admin_del, ed1, ed2);
352 Mmsg(query, select_migrate_del, ed1, ed2);
356 Dmsg1(150, "Query=%s\n", query.c_str());
357 if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
358 ua->error_msg("%s", db_strerror(ua->db));
361 purge_job_list_from_catalog(ua, del);
363 if (del.num_del > 0) {
364 ua->info_msg(_("Pruned %d %s for client %s from catalog.\n"), del.num_del,
365 del.num_del==1?_("Job"):_("Jobs"), client->name());
366 } else if (ua->verbose) {
367 ua->info_msg(_("No Jobs found to prune.\n"));
371 drop_temp_tables(ua);
376 if (del.PurgedFiles) {
377 free(del.PurgedFiles);
383 * Prune a given Volume
385 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
387 POOL_MEM query(PM_MESSAGE);
392 if (mr->Enabled == 2) {
393 return false; /* Cannot prune archived volumes */
396 memset(&del, 0, sizeof(del));
398 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
402 /* Prune only Volumes with status "Full", or "Used" */
403 if (strcmp(mr->VolStatus, "Full") == 0 ||
404 strcmp(mr->VolStatus, "Used") == 0) {
405 Dmsg2(050, "get prune list MediaId=%d Volume %s\n", (int)mr->MediaId, mr->VolumeName);
406 count = get_prune_list_for_volume(ua, mr, &del);
407 Dmsg1(050, "Num pruned = %d\n", count);
409 purge_job_list_from_catalog(ua, del);
411 ok = is_volume_purged(ua, mr);
422 * Get prune list for a volume
424 int get_prune_list_for_volume(UAContext *ua, MEDIA_DBR *mr, del_ctx *del)
426 POOL_MEM query(PM_MESSAGE);
430 char ed1[50], ed2[50];
432 if (mr->Enabled == 2) {
433 return 0; /* cannot prune Archived volumes */
439 * Now add to the list of JobIds for Jobs written to this Volume
441 edit_int64(mr->MediaId, ed1);
442 period = mr->VolRetention;
443 now = (utime_t)time(NULL);
444 edit_uint64(now-period, ed2);
445 Mmsg(query, sel_JobMedia, ed1, ed2);
446 Dmsg3(250, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
449 Dmsg1(050, "Query=%s\n", query.c_str());
450 if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)del)) {
452 ua->error_msg("%s", db_strerror(ua->db));
454 Dmsg0(050, "Count failed\n");
458 for (i=0; i < del->num_ids; i++) {
459 if (ua->jcr->JobId == del->JobId[i]) {
460 Dmsg2(150, "skip same job JobId[%d]=%d\n", i, (int)del->JobId[i]);
464 Dmsg2(150, "accept JobId[%d]=%d\n", i, (int)del->JobId[i]);