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
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)
262 /* Create temp tables and indicies */
263 if (!db_sql_query(ua->db, create_deltabs[db_type], NULL, (void *)NULL)) {
264 ua->error_msg("%s", db_strerror(ua->db));
265 Dmsg0(050, "create DelTables table failed\n");
268 if (!db_sql_query(ua->db, create_delindex, NULL, (void *)NULL)) {
269 ua->error_msg("%s", db_strerror(ua->db));
270 Dmsg0(050, "create DelInx1 index failed\n");
279 * Pruning Jobs is a bit more complicated than purging Files
280 * because we delete Job records only if there is a more current
281 * backup of the FileSet. Otherwise, we keep the Job record.
282 * In other words, we never delete the only Job record that
283 * contains a current backup of a FileSet. This prevents the
284 * Volume from being recycled and destroying a current backup.
286 * For Verify Jobs, we do not delete the last InitCatalog.
288 * For Restore Jobs there are no restrictions.
290 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
293 POOL_MEM query(PM_MESSAGE);
296 char ed1[50], ed2[50];
299 memset(&cr, 0, sizeof(cr));
300 memset(&del, 0, sizeof(del));
302 bstrncpy(cr.Name, client->name(), sizeof(cr.Name));
303 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
308 period = client->JobRetention;
309 now = (utime_t)time(NULL);
311 /* Drop any previous temporary tables still there */
312 drop_temp_tables(ua);
314 /* Create temp tables and indicies */
315 if (!create_temp_tables(ua)) {
320 * Select all files that are older than the JobRetention period
321 * and stuff them into the "DeletionCandidates" table.
323 edit_uint64(now - period, ed1);
324 Mmsg(query, insert_delcand, (char)JobType, ed1,
325 edit_int64(cr.ClientId, ed2));
326 if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) {
328 ua->error_msg("%s", db_strerror(ua->db));
330 Dmsg0(050, "insert delcand failed\n");
335 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
336 del.PurgedFiles = (char *)malloc(del.max_ids);
339 edit_int64(cr.ClientId, ed2);
342 Mmsg(query, select_backup_del, ed1, ed2);
345 Mmsg(query, select_restore_del, ed1, ed2);
348 Mmsg(query, select_verify_del, ed1, ed2);
351 Mmsg(query, select_admin_del, ed1, ed2);
354 Mmsg(query, select_migrate_del, ed1, ed2);
358 Dmsg1(150, "Query=%s\n", query.c_str());
359 if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
360 ua->error_msg("%s", db_strerror(ua->db));
363 purge_job_list_from_catalog(ua, del);
365 if (del.num_del > 0) {
366 ua->info_msg(_("Pruned %d %s for client %s from catalog.\n"), del.num_del,
367 del.num_del==1?_("Job"):_("Jobs"), client->name());
368 } else if (ua->verbose) {
369 ua->info_msg(_("No Jobs found to prune.\n"));
373 drop_temp_tables(ua);
378 if (del.PurgedFiles) {
379 free(del.PurgedFiles);
385 * Prune a given Volume
387 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
389 POOL_MEM query(PM_MESSAGE);
394 if (mr->Enabled == 2) {
395 return false; /* Cannot prune archived volumes */
398 memset(&del, 0, sizeof(del));
400 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
404 /* Prune only Volumes with status "Full", or "Used" */
405 if (strcmp(mr->VolStatus, "Full") == 0 ||
406 strcmp(mr->VolStatus, "Used") == 0) {
407 Dmsg2(050, "get prune list MediaId=%d Volume %s\n", (int)mr->MediaId, mr->VolumeName);
408 count = get_prune_list_for_volume(ua, mr, &del);
409 Dmsg1(050, "Num pruned = %d\n", count);
411 purge_job_list_from_catalog(ua, del);
413 ok = is_volume_purged(ua, mr);
424 * Get prune list for a volume
426 int get_prune_list_for_volume(UAContext *ua, MEDIA_DBR *mr, del_ctx *del)
428 POOL_MEM query(PM_MESSAGE);
432 char ed1[50], ed2[50];
434 if (mr->Enabled == 2) {
435 return 0; /* cannot prune Archived volumes */
441 * Now add to the list of JobIds for Jobs written to this Volume
443 edit_int64(mr->MediaId, ed1);
444 period = mr->VolRetention;
445 now = (utime_t)time(NULL);
446 edit_uint64(now-period, ed2);
447 Mmsg(query, sel_JobMedia, ed1, ed2);
448 Dmsg3(250, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
451 Dmsg1(050, "Query=%s\n", query.c_str());
452 if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)del)) {
454 ua->error_msg("%s", db_strerror(ua->db));
456 Dmsg0(050, "Count failed\n");
460 for (i=0; i < del->num_ids; i++) {
461 if (ua->jcr->JobId == del->JobId[i]) {
462 Dmsg2(150, "skip same job JobId[%d]=%d\n", i, (int)del->JobId[i]);
466 Dmsg2(150, "accept JobId[%d]=%d\n", i, (int)del->JobId[i]);