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-2005 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 void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr );
36 int purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr);
37 void purge_files_from_job(UAContext *ua, JOB_DBR *jr);
38 int mark_media_purged(UAContext *ua, MEDIA_DBR *mr);
40 #define MAX_DEL_LIST_LEN 1000000
43 static const char *select_jobsfiles_from_client =
44 "SELECT JobId FROM Job "
48 static const char *select_jobs_from_client =
49 "SELECT JobId, PurgedFiles FROM Job "
53 /* In memory list of JobIds */
54 struct s_file_del_ctx {
56 int num_ids; /* ids stored */
57 int max_ids; /* size of array */
58 int num_del; /* number deleted */
59 int tot_ids; /* total to process */
62 struct s_job_del_ctx {
63 JobId_t *JobId; /* array of JobIds */
64 char *PurgedFiles; /* Array of PurgedFile flags */
65 int num_ids; /* ids stored */
66 int max_ids; /* size of array */
67 int num_del; /* number deleted */
68 int tot_ids; /* total to process */
76 * Called here to count entries to be deleted
78 static int count_handler(void *ctx, int num_fields, char **row)
80 struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
83 cnt->count = str_to_int64(row[0]);
91 * Called here to count entries to be deleted
93 static int file_count_handler(void *ctx, int num_fields, char **row)
95 struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
101 static int job_count_handler(void *ctx, int num_fields, char **row)
103 struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
110 * Called here to make in memory list of JobIds to be
111 * deleted and the associated PurgedFiles flag.
112 * The in memory list will then be transversed
113 * to issue the SQL DELETE commands. Note, the list
114 * is allowed to get to MAX_DEL_LIST_LEN to limit the
115 * maximum malloc'ed memory.
117 static int job_delete_handler(void *ctx, int num_fields, char **row)
119 struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
121 if (del->num_ids == MAX_DEL_LIST_LEN) {
124 if (del->num_ids == del->max_ids) {
125 del->max_ids = (del->max_ids * 3) / 2;
126 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
127 del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
129 del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
130 del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[0]);
134 static int file_delete_handler(void *ctx, int num_fields, char **row)
136 struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
138 if (del->num_ids == MAX_DEL_LIST_LEN) {
141 if (del->num_ids == del->max_ids) {
142 del->max_ids = (del->max_ids * 3) / 2;
143 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
146 del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
151 * Purge records from database
153 * Purge Files (from) [Job|JobId|Client|Volume]
154 * Purge Jobs (from) [Client|Volume]
156 * N.B. Not all above is implemented yet.
158 int purgecmd(UAContext *ua, const char *cmd)
164 static const char *keywords[] = {
170 static const char *files_keywords[] = {
177 static const char *jobs_keywords[] = {
183 "\nThis command is can be DANGEROUS!!!\n\n"
184 "It purges (deletes) all Files from a Job,\n"
185 "JobId, Client or Volume; or it purges (deletes)\n"
186 "all Jobs from a Client or Volume without regard\n"
187 "for retention periods. Normally you should use the\n"
188 "PRUNE command, which respects retention periods.\n"));
193 switch (find_arg_keyword(ua, keywords)) {
196 switch(find_arg_keyword(ua, files_keywords)) {
199 if (get_job_dbr(ua, &jr)) {
200 purge_files_from_job(ua, &jr);
204 client = get_client_resource(ua);
206 purge_files_from_client(ua, client);
210 if (select_media_dbr(ua, &mr)) {
211 purge_files_from_volume(ua, &mr);
217 switch(find_arg_keyword(ua, jobs_keywords)) {
219 client = get_client_resource(ua);
221 purge_jobs_from_client(ua, client);
225 if (select_media_dbr(ua, &mr)) {
226 purge_jobs_from_volume(ua, &mr);
232 while ((i=find_arg(ua, _("volume"))) >= 0) {
233 if (select_media_dbr(ua, &mr)) {
234 purge_jobs_from_volume(ua, &mr);
236 *ua->argk[i] = 0; /* zap keyword already seen */
243 switch (do_keyword_prompt(ua, _("Choose item to purge"), keywords)) {
245 client = get_client_resource(ua);
247 purge_files_from_client(ua, client);
251 client = get_client_resource(ua);
253 purge_jobs_from_client(ua, client);
257 if (select_media_dbr(ua, &mr)) {
258 purge_jobs_from_volume(ua, &mr);
266 * Purge File records from the database. For any Job which
267 * is older than the retention period, we unconditionally delete
268 * all File records for that Job. This is simple enough that no
269 * temporary tables are needed. We simply make an in memory list of
270 * the JobIds meeting the prune conditions, then delete all File records
271 * pointing to each of those JobIds.
273 static int purge_files_from_client(UAContext *ua, CLIENT *client)
275 struct s_file_del_ctx del;
276 char *query = (char *)get_pool_memory(PM_MESSAGE);
281 memset(&cr, 0, sizeof(cr));
282 memset(&del, 0, sizeof(del));
284 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
285 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
288 bsendmsg(ua, _("Begin purging files for Client \"%s\"\n"), cr.Name);
289 Mmsg(query, select_jobsfiles_from_client, edit_int64(cr.ClientId, ed1));
291 Dmsg1(050, "select sql=%s\n", query);
293 if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
294 bsendmsg(ua, "%s", db_strerror(ua->db));
295 Dmsg0(050, "Count failed\n");
299 if (del.tot_ids == 0) {
300 bsendmsg(ua, _("No Files found for client %s to purge from %s catalog.\n"),
301 client->hdr.name, client->catalog->hdr.name);
305 if (del.tot_ids < MAX_DEL_LIST_LEN) {
306 del.max_ids = del.tot_ids + 1;
308 del.max_ids = MAX_DEL_LIST_LEN;
312 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
314 db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
316 for (i=0; i < del.num_ids; i++) {
317 edit_int64(del.JobId[i], ed1);
318 Dmsg1(050, "Delete JobId=%s\n", ed1);
319 Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
320 db_sql_query(ua->db, query, NULL, (void *)NULL);
322 * Now mark Job as having files purged. This is necessary to
323 * avoid having too many Jobs to process in future prunings. If
324 * we don't do this, the number of JobId's in our in memory list
325 * will grow very large.
327 Mmsg(query, "UPDATE Job Set PurgedFiles=1 WHERE JobId=%s", ed1);
328 db_sql_query(ua->db, query, NULL, (void *)NULL);
329 Dmsg1(050, "Del sql=%s\n", query);
331 bsendmsg(ua, _("%d Files for client \"%s\" purged from %s catalog.\n"), del.num_ids,
332 client->hdr.name, client->catalog->hdr.name);
338 free_pool_memory(query);
345 * Purge Job records from the database. For any Job which
346 * is older than the retention period, we unconditionally delete
347 * it and all File records for that Job. This is simple enough that no
348 * temporary tables are needed. We simply make an in memory list of
349 * the JobIds meeting the prune conditions, then delete the Job,
350 * Files, and JobMedia records in that list.
352 static int purge_jobs_from_client(UAContext *ua, CLIENT *client)
354 struct s_job_del_ctx del;
355 char *query = (char *)get_pool_memory(PM_MESSAGE);
360 memset(&cr, 0, sizeof(cr));
361 memset(&del, 0, sizeof(del));
363 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
364 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
368 bsendmsg(ua, _("Begin purging jobs from Client \"%s\"\n"), cr.Name);
369 Mmsg(query, select_jobs_from_client, edit_int64(cr.ClientId, ed1));
371 Dmsg1(050, "select sql=%s\n", query);
373 if (!db_sql_query(ua->db, query, job_count_handler, (void *)&del)) {
374 bsendmsg(ua, "%s", db_strerror(ua->db));
375 Dmsg0(050, "Count failed\n");
378 if (del.tot_ids == 0) {
379 bsendmsg(ua, _("No Jobs found for client %s to purge from %s catalog.\n"),
380 client->hdr.name, client->catalog->hdr.name);
384 if (del.tot_ids < MAX_DEL_LIST_LEN) {
385 del.max_ids = del.tot_ids + 1;
387 del.max_ids = MAX_DEL_LIST_LEN;
392 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
393 del.PurgedFiles = (char *)malloc(del.max_ids);
395 db_sql_query(ua->db, query, job_delete_handler, (void *)&del);
398 * OK, now we have the list of JobId's to be purged, first check
399 * if the Files have been purged, if not, purge (delete) them.
400 * Then delete the Job entry, and finally and JobMedia records.
402 for (i=0; i < del.num_ids; i++) {
403 edit_int64(del.JobId[i], ed1);
404 Dmsg1(050, "Delete JobId=%s\n", ed1);
405 if (!del.PurgedFiles[i]) {
406 Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
407 db_sql_query(ua->db, query, NULL, (void *)NULL);
408 Dmsg1(050, "Del sql=%s\n", query);
411 Mmsg(query, "DELETE FROM Job WHERE JobId=%s", ed1);
412 db_sql_query(ua->db, query, NULL, (void *)NULL);
413 Dmsg1(050, "Del sql=%s\n", query);
415 Mmsg(query, "DELETE FROM JobMedia WHERE JobId=%s", ed1);
416 db_sql_query(ua->db, query, NULL, (void *)NULL);
417 Dmsg1(050, "Del sql=%s\n", query);
419 bsendmsg(ua, _("%d Jobs for client %s purged from %s catalog.\n"), del.num_ids,
420 client->hdr.name, client->catalog->hdr.name);
426 if (del.PurgedFiles) {
427 free(del.PurgedFiles);
429 free_pool_memory(query);
433 void purge_files_from_job(UAContext *ua, JOB_DBR *jr)
435 char *query = (char *)get_pool_memory(PM_MESSAGE);
438 edit_int64(jr->JobId,ed1);
439 Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
440 db_sql_query(ua->db, query, NULL, (void *)NULL);
442 Mmsg(query, "UPDATE Job Set PurgedFiles=1 WHERE JobId=%s", ed1);
443 db_sql_query(ua->db, query, NULL, (void *)NULL);
445 free_pool_memory(query);
448 void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr )
449 {} /* ***FIXME*** implement */
452 * Returns: 1 if Volume purged
453 * 0 if Volume not purged
455 int purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr)
457 char *query = (char *)get_pool_memory(PM_MESSAGE);
458 struct s_count_ctx cnt;
459 struct s_file_del_ctx del;
464 stat = strcmp(mr->VolStatus, "Append") == 0 ||
465 strcmp(mr->VolStatus, "Full") == 0 ||
466 strcmp(mr->VolStatus, "Used") == 0 ||
467 strcmp(mr->VolStatus, "Error") == 0;
470 bsendmsg(ua, _("Volume \"%s\" has VolStatus \"%s\" and cannot be purged.\n"
471 "The VolStatus must be: Append, Full, Used, or Error to be purged.\n"),
472 mr->VolumeName, mr->VolStatus);
476 memset(&jr, 0, sizeof(jr));
477 memset(&del, 0, sizeof(del));
479 Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%s",
480 edit_int64(mr->MediaId, ed1));
481 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
482 bsendmsg(ua, "%s", db_strerror(ua->db));
483 Dmsg0(050, "Count failed\n");
487 if (cnt.count == 0) {
488 bsendmsg(ua, "There are no Jobs associated with Volume \"%s\". Marking it purged.\n",
490 if (!mark_media_purged(ua, mr)) {
491 bsendmsg(ua, "%s", db_strerror(ua->db));
497 if (cnt.count < MAX_DEL_LIST_LEN) {
498 del.max_ids = cnt.count + 1;
500 del.max_ids = MAX_DEL_LIST_LEN;
504 * Check if he wants to purge a single jobid
506 i = find_arg_with_value(ua, "jobid");
508 del.JobId = (JobId_t *)malloc(sizeof(JobId_t));
510 del.JobId[0] = str_to_int64(ua->argv[i]);
515 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
517 Mmsg(query, "SELECT JobId FROM JobMedia WHERE MediaId=%s",
518 edit_int64(mr->MediaId, ed1));
519 if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
520 bsendmsg(ua, "%s", db_strerror(ua->db));
521 Dmsg0(050, "Count failed\n");
526 for (i=0; i < del.num_ids; i++) {
527 edit_int64(del.JobId[i], ed1);
528 Dmsg1(050, "Delete JobId=%s\n", ed1);
529 Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
530 db_sql_query(ua->db, query, NULL, (void *)NULL);
531 Mmsg(query, "DELETE FROM Job WHERE JobId=%s", ed1);
532 db_sql_query(ua->db, query, NULL, (void *)NULL);
533 Mmsg(query, "DELETE FROM JobMedia WHERE JobId=%s", ed1);
534 db_sql_query(ua->db, query, NULL, (void *)NULL);
535 Dmsg1(050, "Del sql=%s\n", query);
541 bsendmsg(ua, _("%d File%s on Volume \"%s\" purged from catalog.\n"), del.num_del,
542 del.num_del==1?"":"s", mr->VolumeName);
544 /* If purged, mark it so */
546 Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%s",
547 edit_int64(mr->MediaId, ed1));
548 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
549 bsendmsg(ua, "%s", db_strerror(ua->db));
550 Dmsg0(050, "Count failed\n");
554 if (cnt.count == 0) {
555 bsendmsg(ua, "There are no more Jobs associated with Volume \"%s\". Marking it purged.\n",
557 if (!(stat = mark_media_purged(ua, mr))) {
558 bsendmsg(ua, "%s", db_strerror(ua->db));
564 free_pool_memory(query);
569 * IF volume status is Append, Full, Used, or Error, mark it Purged
570 * Purged volumes can then be recycled (if enabled).
572 int 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(ua->jcr, ua->db, mr)) {
584 bsendmsg(ua, _("Cannot purge Volume with VolStatus=%s\n"), mr->VolStatus);
586 return strcpy(mr->VolStatus, "Purged") == 0;