3 * Bacula Director -- User Agent Database Purge Command
5 * Purges Files from specific JobIds
7 * Purges Jobs from Volumes
9 * Kern Sibbald, February MMII
14 Copyright (C) 2002-2006 Kern Sibbald
16 This program is free software; you can redistribute it and/or
17 modify it under the terms of the GNU General Public License
18 version 2 as amended with additional clauses defined in the
19 file LICENSE in the main source directory.
21 This program is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 the file LICENSE for additional details.
31 /* Forward referenced functions */
32 static int purge_files_from_client(UAContext *ua, CLIENT *client);
33 static int purge_jobs_from_client(UAContext *ua, CLIENT *client);
35 #define MAX_DEL_LIST_LEN 1000000
37 static const char *select_jobsfiles_from_client =
38 "SELECT JobId FROM Job "
42 static const char *select_jobs_from_client =
43 "SELECT JobId, PurgedFiles FROM Job "
47 /* In memory list of JobIds */
48 struct s_file_del_ctx {
50 int num_ids; /* ids stored */
51 int max_ids; /* size of array */
52 int num_del; /* number deleted */
53 int tot_ids; /* total to process */
56 struct s_job_del_ctx {
57 JobId_t *JobId; /* array of JobIds */
58 char *PurgedFiles; /* Array of PurgedFile flags */
59 int num_ids; /* ids stored */
60 int max_ids; /* size of array */
61 int num_del; /* number deleted */
62 int tot_ids; /* total to process */
70 * Called here to count entries to be deleted
72 static int count_handler(void *ctx, int num_fields, char **row)
74 struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
77 cnt->count = str_to_int64(row[0]);
85 * Called here to count entries to be deleted
87 static int file_count_handler(void *ctx, int num_fields, char **row)
89 struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
95 static int job_count_handler(void *ctx, int num_fields, char **row)
97 struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
104 * Called here to make in memory list of JobIds to be
105 * deleted and the associated PurgedFiles flag.
106 * The in memory list will then be transversed
107 * to issue the SQL DELETE commands. Note, the list
108 * is allowed to get to MAX_DEL_LIST_LEN to limit the
109 * maximum malloc'ed memory.
111 static int job_delete_handler(void *ctx, int num_fields, char **row)
113 struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
115 if (del->num_ids == MAX_DEL_LIST_LEN) {
118 if (del->num_ids == del->max_ids) {
119 del->max_ids = (del->max_ids * 3) / 2;
120 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
121 del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
123 del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
124 del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[0]);
128 static int file_delete_handler(void *ctx, int num_fields, char **row)
130 struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
132 if (del->num_ids == MAX_DEL_LIST_LEN) {
135 if (del->num_ids == del->max_ids) {
136 del->max_ids = (del->max_ids * 3) / 2;
137 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
140 del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
145 * Purge records from database
147 * Purge Files (from) [Job|JobId|Client|Volume]
148 * Purge Jobs (from) [Client|Volume]
150 * N.B. Not all above is implemented yet.
152 int purgecmd(UAContext *ua, const char *cmd)
158 static const char *keywords[] = {
164 static const char *files_keywords[] = {
171 static const char *jobs_keywords[] = {
177 "\nThis command is can be DANGEROUS!!!\n\n"
178 "It purges (deletes) all Files from a Job,\n"
179 "JobId, Client or Volume; or it purges (deletes)\n"
180 "all Jobs from a Client or Volume without regard\n"
181 "for retention periods. Normally you should use the\n"
182 "PRUNE command, which respects retention periods.\n"));
187 switch (find_arg_keyword(ua, keywords)) {
190 switch(find_arg_keyword(ua, files_keywords)) {
193 if (get_job_dbr(ua, &jr)) {
194 purge_files_from_job(ua, &jr);
198 client = get_client_resource(ua);
200 purge_files_from_client(ua, client);
204 if (select_media_dbr(ua, &mr)) {
205 purge_files_from_volume(ua, &mr);
211 switch(find_arg_keyword(ua, jobs_keywords)) {
213 client = get_client_resource(ua);
215 purge_jobs_from_client(ua, client);
219 if (select_media_dbr(ua, &mr)) {
220 purge_jobs_from_volume(ua, &mr);
226 while ((i=find_arg(ua, NT_("volume"))) >= 0) {
227 if (select_media_dbr(ua, &mr)) {
228 purge_jobs_from_volume(ua, &mr);
230 *ua->argk[i] = 0; /* zap keyword already seen */
237 switch (do_keyword_prompt(ua, _("Choose item to purge"), keywords)) {
239 client = get_client_resource(ua);
241 purge_files_from_client(ua, client);
245 client = get_client_resource(ua);
247 purge_jobs_from_client(ua, client);
251 if (select_media_dbr(ua, &mr)) {
252 purge_jobs_from_volume(ua, &mr);
260 * Purge File records from the database. For any Job which
261 * is older than the retention period, we unconditionally delete
262 * all File records for that Job. This is simple enough that no
263 * temporary tables are needed. We simply make an in memory list of
264 * the JobIds meeting the prune conditions, then delete all File records
265 * pointing to each of those JobIds.
267 static int purge_files_from_client(UAContext *ua, CLIENT *client)
269 struct s_file_del_ctx del;
270 POOLMEM *query = get_pool_memory(PM_MESSAGE);
275 memset(&cr, 0, sizeof(cr));
276 memset(&del, 0, sizeof(del));
278 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
279 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
282 bsendmsg(ua, _("Begin purging files for Client \"%s\"\n"), cr.Name);
283 Mmsg(query, select_jobsfiles_from_client, edit_int64(cr.ClientId, ed1));
285 Dmsg1(050, "select sql=%s\n", query);
287 if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
288 bsendmsg(ua, "%s", db_strerror(ua->db));
289 Dmsg0(050, "Count failed\n");
293 if (del.tot_ids == 0) {
294 bsendmsg(ua, _("No Files found for client %s to purge from %s catalog.\n"),
295 client->hdr.name, client->catalog->hdr.name);
299 if (del.tot_ids < MAX_DEL_LIST_LEN) {
300 del.max_ids = del.tot_ids + 1;
302 del.max_ids = MAX_DEL_LIST_LEN;
306 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
308 db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
310 for (i=0; i < del.num_ids; i++) {
311 edit_int64(del.JobId[i], ed1);
312 Dmsg1(050, "Delete Files JobId=%s\n", ed1);
313 Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
314 db_sql_query(ua->db, query, NULL, (void *)NULL);
316 * Now mark Job as having files purged. This is necessary to
317 * avoid having too many Jobs to process in future prunings. If
318 * we don't do this, the number of JobId's in our in memory list
319 * will grow very large.
321 Mmsg(query, "UPDATE Job Set PurgedFiles=1 WHERE JobId=%s", ed1);
322 db_sql_query(ua->db, query, NULL, (void *)NULL);
323 Dmsg1(050, "Update Purged sql=%s\n", query);
325 bsendmsg(ua, _("%d Files for client \"%s\" purged from %s catalog.\n"), del.num_ids,
326 client->hdr.name, client->catalog->hdr.name);
332 free_pool_memory(query);
339 * Purge Job records from the database. For any Job which
340 * is older than the retention period, we unconditionally delete
341 * it and all File records for that Job. This is simple enough that no
342 * temporary tables are needed. We simply make an in memory list of
343 * the JobIds then delete the Job, Files, and JobMedia records in that list.
345 static int purge_jobs_from_client(UAContext *ua, CLIENT *client)
347 struct s_job_del_ctx del;
348 POOLMEM *query = get_pool_memory(PM_MESSAGE);
353 memset(&cr, 0, sizeof(cr));
354 memset(&del, 0, sizeof(del));
356 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
357 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
361 bsendmsg(ua, _("Begin purging jobs from Client \"%s\"\n"), cr.Name);
362 Mmsg(query, select_jobs_from_client, edit_int64(cr.ClientId, ed1));
364 Dmsg1(050, "select sql=%s\n", query);
366 if (!db_sql_query(ua->db, query, job_count_handler, (void *)&del)) {
367 bsendmsg(ua, "%s", db_strerror(ua->db));
368 Dmsg0(050, "Count failed\n");
371 if (del.tot_ids == 0) {
372 bsendmsg(ua, _("No Jobs found for client %s to purge from %s catalog.\n"),
373 client->hdr.name, client->catalog->hdr.name);
377 if (del.tot_ids < MAX_DEL_LIST_LEN) {
378 del.max_ids = del.tot_ids + 1;
380 del.max_ids = MAX_DEL_LIST_LEN;
385 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
386 del.PurgedFiles = (char *)malloc(del.max_ids);
388 db_sql_query(ua->db, query, job_delete_handler, (void *)&del);
391 * OK, now we have the list of JobId's to be purged, first check
392 * if the Files have been purged, if not, purge (delete) them.
393 * Then delete the Job entry, and finally and JobMedia records.
395 for (i=0; i < del.num_ids; i++) {
396 edit_int64(del.JobId[i], ed1);
397 Dmsg1(050, "Delete Files JobId=%s\n", ed1);
398 if (!del.PurgedFiles[i]) {
399 Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
400 db_sql_query(ua->db, query, NULL, (void *)NULL);
401 Dmsg1(050, "Del sql=%s\n", query);
404 Mmsg(query, "DELETE FROM Job WHERE JobId=%s", ed1);
405 db_sql_query(ua->db, query, NULL, (void *)NULL);
406 Dmsg1(050, "Delete Job sql=%s\n", query);
408 Mmsg(query, "DELETE FROM MAC WHERE JobId=%s", ed1);
409 db_sql_query(ua->db, query, NULL, (void *)NULL);
410 Dmsg1(050, "Delete MAC sql=%s\n", query);
412 Mmsg(query, "DELETE FROM JobMedia WHERE JobId=%s", ed1);
413 db_sql_query(ua->db, query, NULL, (void *)NULL);
414 Dmsg1(050, "Delete JobMedia sql=%s\n", query);
416 bsendmsg(ua, _("%d Jobs for client %s purged from %s catalog.\n"), del.num_ids,
417 client->hdr.name, client->catalog->hdr.name);
423 if (del.PurgedFiles) {
424 free(del.PurgedFiles);
426 free_pool_memory(query);
430 void purge_files_from_job(UAContext *ua, JOB_DBR *jr)
432 POOLMEM *query = get_pool_memory(PM_MESSAGE);
435 edit_int64(jr->JobId, ed1);
436 Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
437 db_sql_query(ua->db, query, NULL, (void *)NULL);
439 Mmsg(query, "UPDATE Job Set PurgedFiles=1 WHERE JobId=%s", ed1);
440 db_sql_query(ua->db, query, NULL, (void *)NULL);
442 free_pool_memory(query);
445 void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr )
446 {} /* ***FIXME*** implement */
449 * Returns: 1 if Volume purged
450 * 0 if Volume not purged
452 int purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr)
454 POOLMEM *query = get_pool_memory(PM_MESSAGE);
455 struct s_count_ctx cnt;
456 struct s_file_del_ctx del;
461 stat = strcmp(mr->VolStatus, "Append") == 0 ||
462 strcmp(mr->VolStatus, "Full") == 0 ||
463 strcmp(mr->VolStatus, "Used") == 0 ||
464 strcmp(mr->VolStatus, "Error") == 0;
467 bsendmsg(ua, _("Volume \"%s\" has VolStatus \"%s\" and cannot be purged.\n"
468 "The VolStatus must be: Append, Full, Used, or Error to be purged.\n"),
469 mr->VolumeName, mr->VolStatus);
473 memset(&jr, 0, sizeof(jr));
474 memset(&del, 0, sizeof(del));
476 Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%s",
477 edit_int64(mr->MediaId, ed1));
478 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
479 bsendmsg(ua, "%s", db_strerror(ua->db));
480 Dmsg0(050, "Count failed\n");
484 if (cnt.count == 0) {
485 bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
487 if (!mark_media_purged(ua, mr)) {
488 bsendmsg(ua, "%s", db_strerror(ua->db));
494 if (cnt.count < MAX_DEL_LIST_LEN) {
495 del.max_ids = cnt.count + 1;
497 del.max_ids = MAX_DEL_LIST_LEN;
501 * Check if he wants to purge a single jobid
503 i = find_arg_with_value(ua, "jobid");
505 del.JobId = (JobId_t *)malloc(sizeof(JobId_t));
507 del.JobId[0] = str_to_int64(ua->argv[i]);
512 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
514 Mmsg(query, "SELECT JobId FROM JobMedia WHERE MediaId=%s",
515 edit_int64(mr->MediaId, ed1));
516 if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
517 bsendmsg(ua, "%s", db_strerror(ua->db));
518 Dmsg0(050, "Count failed\n");
523 for (i=0; i < del.num_ids; i++) {
524 edit_int64(del.JobId[i], ed1);
525 Dmsg1(050, "Delete JobId=%s\n", ed1);
526 Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
527 db_sql_query(ua->db, query, NULL, (void *)NULL);
528 Mmsg(query, "DELETE FROM Job WHERE JobId=%s", ed1);
529 db_sql_query(ua->db, query, NULL, (void *)NULL);
530 Mmsg(query, "DELETE FROM MAC WHERE JobId=%s", ed1);
531 db_sql_query(ua->db, query, NULL, (void *)NULL);
532 Mmsg(query, "DELETE FROM JobMedia WHERE JobId=%s", ed1);
533 db_sql_query(ua->db, query, NULL, (void *)NULL);
534 Dmsg1(050, "Del sql=%s\n", query);
540 bsendmsg(ua, _("%d File%s on Volume \"%s\" purged from catalog.\n"), del.num_del,
541 del.num_del==1?"":"s", mr->VolumeName);
543 /* If purged, mark it so */
545 Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%s",
546 edit_int64(mr->MediaId, ed1));
547 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
548 bsendmsg(ua, "%s", db_strerror(ua->db));
549 Dmsg0(050, "Count failed\n");
553 if (cnt.count == 0) {
554 bsendmsg(ua, _("There are no more Jobs associated with Volume \"%s\". Marking it purged.\n"),
556 if (!(stat = mark_media_purged(ua, mr))) {
557 bsendmsg(ua, "%s", db_strerror(ua->db));
563 free_pool_memory(query);
568 * IF volume status is Append, Full, Used, or Error, mark it Purged
569 * Purged volumes can then be recycled (if enabled).
571 bool mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
574 if (strcmp(mr->VolStatus, "Append") == 0 ||
575 strcmp(mr->VolStatus, "Full") == 0 ||
576 strcmp(mr->VolStatus, "Used") == 0 ||
577 strcmp(mr->VolStatus, "Error") == 0) {
578 bstrncpy(mr->VolStatus, "Purged", sizeof(mr->VolStatus));
579 if (!db_update_media_record(jcr, ua->db, mr)) {
582 pm_strcpy(jcr->VolumeName, mr->VolumeName);
583 generate_job_event(jcr, "VolumePurged");
586 bsendmsg(ua, _("Cannot purge Volume with VolStatus=%s\n"), mr->VolStatus);
588 return strcmp(mr->VolStatus, "Purged") == 0;