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 extern const char *del_File;
32 extern const char *upd_Purged;
34 /* Forward referenced functions */
35 static int purge_files_from_client(UAContext *ua, CLIENT *client);
36 static int purge_jobs_from_client(UAContext *ua, CLIENT *client);
38 #define MAX_DEL_LIST_LEN 1000000
40 static const char *select_jobsfiles_from_client =
41 "SELECT JobId FROM Job "
45 static const char *select_jobs_from_client =
46 "SELECT JobId, PurgedFiles FROM Job "
50 /* In memory list of JobIds */
51 struct s_file_del_ctx {
53 int num_ids; /* ids stored */
54 int max_ids; /* size of array */
55 int num_del; /* number deleted */
56 int tot_ids; /* total to process */
59 struct s_job_del_ctx {
60 JobId_t *JobId; /* array of JobIds */
61 char *PurgedFiles; /* Array of PurgedFile flags */
62 int num_ids; /* ids stored */
63 int max_ids; /* size of array */
64 int num_del; /* number deleted */
65 int tot_ids; /* total to process */
73 * Called here to count entries to be deleted
75 static int count_handler(void *ctx, int num_fields, char **row)
77 struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
80 cnt->count = str_to_int64(row[0]);
88 * Called here to count entries to be deleted
90 static int file_count_handler(void *ctx, int num_fields, char **row)
92 struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
98 static int job_count_handler(void *ctx, int num_fields, char **row)
100 struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
107 * Called here to make in memory list of JobIds to be
108 * deleted and the associated PurgedFiles flag.
109 * The in memory list will then be transversed
110 * to issue the SQL DELETE commands. Note, the list
111 * is allowed to get to MAX_DEL_LIST_LEN to limit the
112 * maximum malloc'ed memory.
114 static int job_delete_handler(void *ctx, int num_fields, char **row)
116 struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
118 if (del->num_ids == MAX_DEL_LIST_LEN) {
121 if (del->num_ids == del->max_ids) {
122 del->max_ids = (del->max_ids * 3) / 2;
123 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
124 del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
126 del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
127 del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[1]);
131 static int file_delete_handler(void *ctx, int num_fields, char **row)
133 struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
135 if (del->num_ids == MAX_DEL_LIST_LEN) {
138 if (del->num_ids == del->max_ids) {
139 del->max_ids = (del->max_ids * 3) / 2;
140 del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
143 del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
148 * Purge records from database
150 * Purge Files (from) [Job|JobId|Client|Volume]
151 * Purge Jobs (from) [Client|Volume]
153 * N.B. Not all above is implemented yet.
155 int purgecmd(UAContext *ua, const char *cmd)
161 static const char *keywords[] = {
167 static const char *files_keywords[] = {
174 static const char *jobs_keywords[] = {
180 "\nThis command is can be DANGEROUS!!!\n\n"
181 "It purges (deletes) all Files from a Job,\n"
182 "JobId, Client or Volume; or it purges (deletes)\n"
183 "all Jobs from a Client or Volume without regard\n"
184 "for retention periods. Normally you should use the\n"
185 "PRUNE command, which respects retention periods.\n"));
190 switch (find_arg_keyword(ua, keywords)) {
193 switch(find_arg_keyword(ua, files_keywords)) {
196 if (get_job_dbr(ua, &jr)) {
197 purge_files_from_job(ua, jr.JobId);
201 client = get_client_resource(ua);
203 purge_files_from_client(ua, client);
207 if (select_media_dbr(ua, &mr)) {
208 purge_files_from_volume(ua, &mr);
214 switch(find_arg_keyword(ua, jobs_keywords)) {
216 client = get_client_resource(ua);
218 purge_jobs_from_client(ua, client);
222 if (select_media_dbr(ua, &mr)) {
223 purge_jobs_from_volume(ua, &mr);
229 while ((i=find_arg(ua, NT_("volume"))) >= 0) {
230 if (select_media_dbr(ua, &mr)) {
231 purge_jobs_from_volume(ua, &mr);
233 *ua->argk[i] = 0; /* zap keyword already seen */
240 switch (do_keyword_prompt(ua, _("Choose item to purge"), keywords)) {
242 client = get_client_resource(ua);
244 purge_files_from_client(ua, client);
248 client = get_client_resource(ua);
250 purge_jobs_from_client(ua, client);
254 if (select_media_dbr(ua, &mr)) {
255 purge_jobs_from_volume(ua, &mr);
263 * Purge File records from the database. For any Job which
264 * is older than the retention period, we unconditionally delete
265 * all File records for that Job. This is simple enough that no
266 * temporary tables are needed. We simply make an in memory list of
267 * the JobIds meeting the prune conditions, then delete all File records
268 * pointing to each of those JobIds.
270 static int purge_files_from_client(UAContext *ua, CLIENT *client)
272 struct s_file_del_ctx del;
273 POOLMEM *query = get_pool_memory(PM_MESSAGE);
278 memset(&cr, 0, sizeof(cr));
279 memset(&del, 0, sizeof(del));
281 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
282 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
285 bsendmsg(ua, _("Begin purging files for Client \"%s\"\n"), cr.Name);
286 Mmsg(query, select_jobsfiles_from_client, edit_int64(cr.ClientId, ed1));
288 Dmsg1(050, "select sql=%s\n", query);
290 if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
291 bsendmsg(ua, "%s", db_strerror(ua->db));
292 Dmsg0(050, "Count failed\n");
296 if (del.tot_ids == 0) {
297 bsendmsg(ua, _("No Files found for client %s to purge from %s catalog.\n"),
298 client->hdr.name, client->catalog->hdr.name);
302 if (del.tot_ids < MAX_DEL_LIST_LEN) {
303 del.max_ids = del.tot_ids + 1;
305 del.max_ids = MAX_DEL_LIST_LEN;
309 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
311 db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
313 for (i=0; i < del.num_ids; i++) {
314 purge_files_from_job(ua, del.JobId[i]);
316 bsendmsg(ua, _("%d Files for client \"%s\" purged from %s catalog.\n"), del.num_ids,
317 client->hdr.name, client->catalog->hdr.name);
323 free_pool_memory(query);
330 * Purge Job records from the database. For any Job which
331 * is older than the retention period, we unconditionally delete
332 * it and all File records for that Job. This is simple enough that no
333 * temporary tables are needed. We simply make an in memory list of
334 * the JobIds then delete the Job, Files, and JobMedia records in that list.
336 static int purge_jobs_from_client(UAContext *ua, CLIENT *client)
338 struct s_job_del_ctx del;
339 POOLMEM *query = get_pool_memory(PM_MESSAGE);
344 memset(&cr, 0, sizeof(cr));
345 memset(&del, 0, sizeof(del));
347 bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
348 if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
352 bsendmsg(ua, _("Begin purging jobs from Client \"%s\"\n"), cr.Name);
353 Mmsg(query, select_jobs_from_client, edit_int64(cr.ClientId, ed1));
355 Dmsg1(050, "select sql=%s\n", query);
357 if (!db_sql_query(ua->db, query, job_count_handler, (void *)&del)) {
358 bsendmsg(ua, "%s", db_strerror(ua->db));
359 Dmsg0(050, "Count failed\n");
362 if (del.tot_ids == 0) {
363 bsendmsg(ua, _("No Jobs found for client %s to purge from %s catalog.\n"),
364 client->hdr.name, client->catalog->hdr.name);
368 if (del.tot_ids < MAX_DEL_LIST_LEN) {
369 del.max_ids = del.tot_ids + 1;
371 del.max_ids = MAX_DEL_LIST_LEN;
376 del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
377 del.PurgedFiles = (char *)malloc(del.max_ids);
379 db_sql_query(ua->db, query, job_delete_handler, (void *)&del);
382 * OK, now we have the list of JobId's to be purged, first check
383 * if the Files have been purged, if not, purge (delete) them.
384 * Then delete the Job entry, and finally and JobMedia records.
386 for (i=0; i < del.num_ids; i++) {
387 Dmsg1(050, "Delete Files JobId=%s\n", ed1);
388 if (!del.PurgedFiles[i]) {
389 purge_files_from_job(ua, del.JobId[i]);
391 purge_job_from_catalog(ua, del.JobId[i]);
393 bsendmsg(ua, _("%d Jobs for client %s purged from %s catalog.\n"), del.num_ids,
394 client->hdr.name, client->catalog->hdr.name);
400 if (del.PurgedFiles) {
401 free(del.PurgedFiles);
403 free_pool_memory(query);
407 void purge_job_from_catalog(UAContext *ua, JobId_t JobId)
409 POOL_MEM query(PM_MESSAGE);
412 edit_int64(JobId, ed1);
413 Mmsg(query, "DELETE FROM Job WHERE JobId=%s", ed1);
414 db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
415 Dmsg1(050, "Delete Job sql=%s\n", query.c_str());
417 Mmsg(query, "DELETE FROM JobMedia WHERE JobId=%s", ed1);
418 db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
419 Dmsg1(050, "Delete JobMedia sql=%s\n", query.c_str());
421 Mmsg(query, "DELETE FROM Log WHERE JobId=%s", ed1);
422 db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
423 Dmsg1(050, "Delete Log sql=%s\n", query.c_str());
427 void purge_files_from_job(UAContext *ua, JobId_t JobId)
429 POOL_MEM query(PM_MESSAGE);
432 edit_int64(JobId, ed1);
433 Mmsg(query, del_File, ed1);
434 db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
437 * Now mark Job as having files purged. This is necessary to
438 * avoid having too many Jobs to process in future prunings. If
439 * we don't do this, the number of JobId's in our in memory list
440 * could grow very large.
442 Mmsg(query, upd_Purged, ed1);
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 purge_files_from_job(ua, del.JobId[i]);
525 purge_job_from_catalog(ua, del.JobId[i]);
531 bsendmsg(ua, _("%d File%s on Volume \"%s\" purged from catalog.\n"), del.num_del,
532 del.num_del==1?"":"s", mr->VolumeName);
534 /* If purged, mark it so */
536 Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%s",
537 edit_int64(mr->MediaId, ed1));
538 if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
539 bsendmsg(ua, "%s", db_strerror(ua->db));
540 Dmsg0(050, "Count failed\n");
544 if (cnt.count == 0) {
545 bsendmsg(ua, _("There are no more Jobs associated with Volume \"%s\". Marking it purged.\n"),
547 if (!(stat = mark_media_purged(ua, mr))) {
548 bsendmsg(ua, "%s", db_strerror(ua->db));
554 free_pool_memory(query);
559 * IF volume status is Append, Full, Used, or Error, mark it Purged
560 * Purged volumes can then be recycled (if enabled).
562 bool mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
565 if (strcmp(mr->VolStatus, "Append") == 0 ||
566 strcmp(mr->VolStatus, "Full") == 0 ||
567 strcmp(mr->VolStatus, "Used") == 0 ||
568 strcmp(mr->VolStatus, "Error") == 0) {
569 bstrncpy(mr->VolStatus, "Purged", sizeof(mr->VolStatus));
570 if (!db_update_media_record(jcr, ua->db, mr)) {
573 pm_strcpy(jcr->VolumeName, mr->VolumeName);
574 generate_job_event(jcr, "VolumePurged");
577 bsendmsg(ua, _("Cannot purge Volume with VolStatus=%s\n"), mr->VolStatus);
579 return strcmp(mr->VolStatus, "Purged") == 0;