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 bsendmsg(ua, _("Cannot prune Volume \"%s\" because it is archived.\n"),
159 if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
162 prune_volume(ua, &mr);
172 * Prune File records from the database. For any Job which
173 * is older than the retention period, we unconditionally delete
174 * all File records for that Job. This is simple enough that no
175 * temporary tables are needed. We simply make an in memory list of
176 * the JobIds meeting the prune conditions, then delete all File records
177 * pointing to each of those JobIds.
179 * This routine assumes you want the pruning to be done. All checking
180 * must be done before calling this routine.
182 int prune_files(UAContext *ua, CLIENT *client)
185 struct s_count_ctx cnt;
186 POOL_MEM query(PM_MESSAGE);
189 char ed1[50], ed2[50];
192 memset(&cr, 0, sizeof(cr));
193 memset(&del, 0, sizeof(del));
194 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
195 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
200 period = client->FileRetention;
201 now = (utime_t)time(NULL);
203 /* Select Jobs -- for counting */
204 Mmsg(query, count_select_job, edit_uint64(now - period, ed1),
205 edit_int64(cr.ClientId, ed2));
206 Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now,
207 (uint32_t)period, query.c_str());
209 if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
210 bsendmsg(ua, "%s", db_strerror(ua->db));
211 Dmsg0(050, "Count failed\n");
215 if (cnt.count == 0) {
217 bsendmsg(ua, _("No Files found to prune.\n"));
222 if (cnt.count < MAX_DEL_LIST_LEN) {
223 del.max_ids = cnt.count + 1;
225 del.max_ids = MAX_DEL_LIST_LEN;
229 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
231 /* Now process same set but making a delete list */
232 Mmsg(query, select_job, edit_uint64(now - period, ed1),
233 edit_int64(cr.ClientId, ed2));
234 db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del);
236 purge_files_from_job_list(ua, del);
238 edit_uint64_with_commas(del.num_del, ed1);
239 bsendmsg(ua, _("Pruned Files from %s Jobs for client %s from catalog.\n"),
240 ed1, client->name());
251 static void drop_temp_tables(UAContext *ua)
254 for (i=0; drop_deltabs[i]; i++) {
255 db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
259 static bool create_temp_tables(UAContext *ua)
262 /* Create temp tables and indicies */
263 for (i=0; create_deltabs[i]; i++) {
264 if (!db_sql_query(ua->db, create_deltabs[i], NULL, (void *)NULL)) {
265 bsendmsg(ua, "%s", db_strerror(ua->db));
266 Dmsg0(050, "create DelTables table failed\n");
276 * Pruning Jobs is a bit more complicated than purging Files
277 * because we delete Job records only if there is a more current
278 * backup of the FileSet. Otherwise, we keep the Job record.
279 * In other words, we never delete the only Job record that
280 * contains a current backup of a FileSet. This prevents the
281 * Volume from being recycled and destroying a current backup.
283 * For Verify Jobs, we do not delete the last InitCatalog.
285 * For Restore Jobs there are no restrictions.
287 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
290 POOL_MEM query(PM_MESSAGE);
293 char ed1[50], ed2[50];
296 memset(&cr, 0, sizeof(cr));
297 memset(&del, 0, sizeof(del));
299 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
300 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
305 period = client->JobRetention;
306 now = (utime_t)time(NULL);
308 /* Drop any previous temporary tables still there */
309 drop_temp_tables(ua);
311 /* Create temp tables and indicies */
312 if (!create_temp_tables(ua)) {
317 * Select all files that are older than the JobRetention period
318 * and stuff them into the "DeletionCandidates" table.
320 edit_uint64(now - period, ed1);
321 Mmsg(query, insert_delcand, (char)JobType, ed1,
322 edit_int64(cr.ClientId, ed2));
323 if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) {
325 bsendmsg(ua, "%s", db_strerror(ua->db));
327 Dmsg0(050, "insert delcand failed\n");
332 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
333 del.PurgedFiles = (char *)malloc(del.max_ids);
336 edit_int64(cr.ClientId, ed2);
339 Mmsg(query, select_backup_del, ed1, ed2);
342 Mmsg(query, select_restore_del, ed1, ed2);
345 Mmsg(query, select_verify_del, ed1, ed2);
348 Mmsg(query, select_admin_del, ed1, ed2);
351 Mmsg(query, select_migrate_del, ed1, ed2);
355 Dmsg1(150, "Query=%s\n", query.c_str());
356 if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
357 bsendmsg(ua, "%s", db_strerror(ua->db));
360 purge_job_list_from_catalog(ua, del);
362 if (del.num_del > 0) {
363 bsendmsg(ua, _("Pruned %d %s for client %s from catalog.\n"), del.num_del,
364 del.num_del==1?_("Job"):_("Jobs"), client->name());
365 } else if (ua->verbose) {
366 bsendmsg(ua, _("No Jobs found to prune.\n"));
370 drop_temp_tables(ua);
375 if (del.PurgedFiles) {
376 free(del.PurgedFiles);
382 * Prune a given Volume
384 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
386 POOL_MEM query(PM_MESSAGE);
387 struct s_count_ctx cnt;
393 char ed1[50], ed2[50];
395 if (mr->Enabled == 2) {
396 return false; /* Cannot prune archived volumes */
400 memset(&jr, 0, sizeof(jr));
401 memset(&del, 0, sizeof(del));
404 * Find out how many Jobs remain on this Volume by
405 * counting the JobMedia records.
408 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
409 Dmsg1(150, "Query=%s\n", query.c_str());
410 if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
411 bsendmsg(ua, "%s", db_strerror(ua->db));
412 Dmsg0(050, "Count failed\n");
416 if (cnt.count == 0) {
417 /* Don't mark appendable volume as purged */
418 if (strcmp(mr->VolStatus, "Append") == 0 && verbose) {
419 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Prune not needed.\n"),
424 /* If volume not already purged, do so */
425 if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
426 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
429 ok = mark_media_purged(ua, mr);
433 if (cnt.count < MAX_DEL_LIST_LEN) {
434 del.max_ids = cnt.count + 1;
436 del.max_ids = MAX_DEL_LIST_LEN;
440 * Now get a list of JobIds for Jobs written to this Volume
442 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
444 /* Use Volume Retention to prune Jobs and their Files */
445 period = mr->VolRetention;
446 now = (utime_t)time(NULL);
447 Mmsg(query, sel_JobMedia, edit_int64(mr->MediaId, ed1),
448 edit_uint64(now-period, ed2));
449 Dmsg3(250, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
452 Dmsg1(150, "Query=%s\n", query.c_str());
453 if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del)) {
455 bsendmsg(ua, "%s", db_strerror(ua->db));
457 Dmsg0(050, "Count failed\n");
463 for (i=0; i < del.num_ids; i++) {
464 if (ua->jcr->JobId == del.JobId[i]) {
465 Dmsg2(250, "skip same job JobId[%d]=%d\n", i, (int)del.JobId[i]);
469 Dmsg2(250, "accept JobId[%d]=%d\n", i, (int)del.JobId[i]);
472 if (cnt.count != 0) {
473 purge_job_list_from_catalog(ua, del);
475 Dmsg0(050, "No jobs to prune.\n");
479 if (ua->verbose && del.num_del != 0) {
480 bsendmsg(ua, _("Pruned %d %s on Volume \"%s\" from catalog.\n"), del.num_del,
481 del.num_del == 1 ? "Job" : "Jobs", mr->VolumeName);
485 * Find out how many Jobs remain on this Volume by
486 * counting the JobMedia records.
489 Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
490 Dmsg1(150, "Query=%s\n", query.c_str());
491 if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
492 bsendmsg(ua, "%s", db_strerror(ua->db));
493 Dmsg0(050, "Count failed\n");
496 if (cnt.count == 0) {
497 Dmsg0(200, "Volume is purged.\n");
498 ok = mark_media_purged(ua, mr);
510 * Get prune list for a volume
512 int get_prune_list_for_volume(UAContext *ua, MEDIA_DBR *mr, del_ctx *del)
514 POOL_MEM query(PM_MESSAGE);
518 char ed1[50], ed2[50];
520 if (mr->Enabled == 2) {
521 return 0; /* cannot prune Archived volumes */
527 * Now add to the list of JobIds for Jobs written to this Volume
529 edit_int64(mr->MediaId, ed1);
530 period = mr->VolRetention;
531 now = (utime_t)time(NULL);
532 edit_uint64(now-period, ed2);
533 Mmsg(query, sel_JobMedia, ed1, ed2);
534 Dmsg3(250, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
537 Dmsg1(050, "Query=%s\n", query.c_str());
538 if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)del)) {
540 bsendmsg(ua, "%s", db_strerror(ua->db));
542 Dmsg0(050, "Count failed\n");
546 for (i=0; i < del->num_ids; i++) {
547 if (ua->jcr->JobId == del->JobId[i]) {
548 Dmsg2(150, "skip same job JobId[%d]=%d\n", i, (int)del->JobId[i]);
552 Dmsg2(150, "accept JobId[%d]=%d\n", i, (int)del->JobId[i]);