X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;ds=sidebyside;f=bacula%2Fsrc%2Fdird%2Fua_purge.c;h=80e2783633411494b589f24cb9a6a4f08289c2ee;hb=ec7eb240abd60e667d1a26f89df1b064e1b3786d;hp=e45b89d3340a74a3ecd06cd46166e7920041af29;hpb=db408b1c519492b0bf19c56ebba5414858c3f5ea;p=bacula%2Fbacula diff --git a/bacula/src/dird/ua_purge.c b/bacula/src/dird/ua_purge.c index e45b89d334..80e2783633 100644 --- a/bacula/src/dird/ua_purge.c +++ b/bacula/src/dird/ua_purge.c @@ -1,157 +1,58 @@ +/* + Bacula® - The Network Backup Solution + + Copyright (C) 2002-2007 Free Software Foundation Europe e.V. + + The main author of Bacula is Kern Sibbald, with contributions from + many others, a complete list can be found in the file AUTHORS. + This program is Free Software; you can redistribute it and/or + modify it under the terms of version two of the GNU General Public + License as published by the Free Software Foundation and included + in the file LICENSE. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. + + Bacula® is a registered trademark of John Walker. + The licensor of Bacula is the Free Software Foundation Europe + (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich, + Switzerland, email:ftf@fsfeurope.org. +*/ /* * * Bacula Director -- User Agent Database Purge Command * - * Purges Files from specific JobIds + * Purges Files from specific JobIds * or - * Purges Jobs from Volumes + * Purges Jobs from Volumes * * Kern Sibbald, February MMII * * Version $Id$ */ -/* - Copyright (C) 2002 Kern Sibbald and John Walker - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public - License along with this program; if not, write to the Free - Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, - MA 02111-1307, USA. - - */ - #include "bacula.h" #include "dird.h" -#include "ua.h" /* Forward referenced functions */ -int purge_files_from_client(UAContext *ua, CLIENT *client); -int purge_jobs_from_client(UAContext *ua, CLIENT *client); -void purge_files_from_volume(UAContext *ua, POOL_DBR *pr, MEDIA_DBR *mr ); -void purge_jobs_from_volume(UAContext *ua, POOL_DBR *pr, MEDIA_DBR *mr); -void purge_files_from_job(UAContext *ua, JOB_DBR *jr); -int mark_media_purged(UAContext *ua, MEDIA_DBR *mr); - -#define MAX_DEL_LIST_LEN 1000000 +static int purge_files_from_client(UAContext *ua, CLIENT *client); +static int purge_jobs_from_client(UAContext *ua, CLIENT *client); - -static char *select_jobsfiles_from_client = +static const char *select_jobsfiles_from_client = "SELECT JobId FROM Job " - "WHERE ClientId=%d " + "WHERE ClientId=%s " "AND PurgedFiles=0"; -static char *select_jobs_from_client = +static const char *select_jobs_from_client = "SELECT JobId, PurgedFiles FROM Job " - "WHERE ClientId=%d"; - - -/* In memory list of JobIds */ -struct s_file_del_ctx { - JobId_t *JobId; - int num_ids; /* ids stored */ - int max_ids; /* size of array */ - int num_del; /* number deleted */ - int tot_ids; /* total to process */ -}; - -struct s_job_del_ctx { - JobId_t *JobId; /* array of JobIds */ - char *PurgedFiles; /* Array of PurgedFile flags */ - int num_ids; /* ids stored */ - int max_ids; /* size of array */ - int num_del; /* number deleted */ - int tot_ids; /* total to process */ -}; - -struct s_count_ctx { - int count; -}; - -/* - * Called here to count entries to be deleted - */ -static int count_handler(void *ctx, int num_fields, char **row) -{ - struct s_count_ctx *cnt = (struct s_count_ctx *)ctx; - - if (row[0]) { - cnt->count = atoi(row[0]); - } else { - cnt->count = 0; - } - return 0; -} - -/* - * Called here to count entries to be deleted - */ -static int file_count_handler(void *ctx, int num_fields, char **row) -{ - struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx; - del->tot_ids++; - return 0; -} - - -static int job_count_handler(void *ctx, int num_fields, char **row) -{ - struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx; - del->tot_ids++; - return 0; -} - - -/* - * Called here to make in memory list of JobIds to be - * deleted and the associated PurgedFiles flag. - * The in memory list will then be transversed - * to issue the SQL DELETE commands. Note, the list - * is allowed to get to MAX_DEL_LIST_LEN to limit the - * maximum malloc'ed memory. - */ -static int job_delete_handler(void *ctx, int num_fields, char **row) -{ - struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx; - - if (del->num_ids == MAX_DEL_LIST_LEN) { - return 1; - } - if (del->num_ids == del->max_ids) { - del->max_ids = (del->max_ids * 3) / 2; - del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids); - del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids); - } - del->JobId[del->num_ids] = (JobId_t)strtod(row[0], NULL); - del->PurgedFiles[del->num_ids++] = (char)atoi(row[0]); - return 0; -} - -static int file_delete_handler(void *ctx, int num_fields, char **row) -{ - struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx; - - if (del->num_ids == MAX_DEL_LIST_LEN) { - return 1; - } - if (del->num_ids == del->max_ids) { - del->max_ids = (del->max_ids * 3) / 2; - del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * - del->max_ids); - } - del->JobId[del->num_ids++] = (JobId_t)strtod(row[0], NULL); - return 0; -} + "WHERE ClientId=%s"; /* * Purge records from database @@ -161,35 +62,37 @@ static int file_delete_handler(void *ctx, int num_fields, char **row) * * N.B. Not all above is implemented yet. */ -int purgecmd(UAContext *ua, char *cmd) +int purgecmd(UAContext *ua, const char *cmd) { + int i; CLIENT *client; MEDIA_DBR mr; - POOL_DBR pr; JOB_DBR jr; - static char *keywords[] = { - N_("files"), - N_("jobs"), + static const char *keywords[] = { + NT_("files"), + NT_("jobs"), + NT_("volume"), NULL}; - static char *files_keywords[] = { - N_("Job"), - N_("JobId"), - N_("Client"), - N_("Volume"), + static const char *files_keywords[] = { + NT_("Job"), + NT_("JobId"), + NT_("Client"), + NT_("Volume"), NULL}; - static char *jobs_keywords[] = { - N_("Client"), - N_("Volume"), + static const char *jobs_keywords[] = { + NT_("Client"), + NT_("Volume"), NULL}; - - bsendmsg(ua, _( - "This command is DANGEROUS!!!\n" + + ua->warning_msg(_( + "\nThis command is can be DANGEROUS!!!\n\n" "It purges (deletes) all Files from a Job,\n" "JobId, Client or Volume; or it purges (deletes)\n" - "all Jobs from a Client or Volume. Normally you\n" - "should use the PRUNE command instead.\n")); + "all Jobs from a Client or Volume without regard\n" + "for retention periods. Normally you should use the\n" + "PRUNE command, which respects retention periods.\n")); if (!open_db(ua)) { return 1; @@ -198,321 +101,443 @@ int purgecmd(UAContext *ua, char *cmd) /* Files */ case 0: switch(find_arg_keyword(ua, files_keywords)) { - case 0: /* Job */ - case 1: - if (get_job_dbr(ua, &jr)) { - purge_files_from_job(ua, &jr); - } - return 1; - case 2: /* client */ - client = select_client_resource(ua); - purge_files_from_client(ua, client); - return 1; - case 3: - if (select_pool_and_media_dbr(ua, &pr, &mr)) { - purge_files_from_volume(ua, &pr, &mr); - } - return 1; + case 0: /* Job */ + case 1: /* JobId */ + if (get_job_dbr(ua, &jr)) { + char jobid[50]; + edit_int64(jr.JobId, jobid); + purge_files_from_jobs(ua, jobid); + } + return 1; + case 2: /* client */ + client = get_client_resource(ua); + if (client) { + purge_files_from_client(ua, client); + } + return 1; + case 3: /* Volume */ + if (select_media_dbr(ua, &mr)) { + purge_files_from_volume(ua, &mr); + } + return 1; } /* Jobs */ case 1: switch(find_arg_keyword(ua, jobs_keywords)) { - case 0: /* client */ - client = select_client_resource(ua); - purge_jobs_from_client(ua, client); - return 1; - case 1: - if (select_pool_and_media_dbr(ua, &pr, &mr)) { - purge_jobs_from_volume(ua, &pr, &mr); - } - return 1; + case 0: /* client */ + client = get_client_resource(ua); + if (client) { + purge_jobs_from_client(ua, client); + } + return 1; + case 1: /* Volume */ + if (select_media_dbr(ua, &mr)) { + purge_jobs_from_volume(ua, &mr); + } + return 1; + } + /* Volume */ + case 2: + while ((i=find_arg(ua, NT_("volume"))) >= 0) { + if (select_media_dbr(ua, &mr)) { + purge_jobs_from_volume(ua, &mr); + } + *ua->argk[i] = 0; /* zap keyword already seen */ + ua->send_msg("\n"); } + return 1; default: break; } switch (do_keyword_prompt(ua, _("Choose item to purge"), keywords)) { - case 0: - client = select_client_resource(ua); - if (!client) { - return 1; + case 0: /* files */ + client = get_client_resource(ua); + if (client) { + purge_files_from_client(ua, client); } - purge_files_from_client(ua, client); break; - case 1: - client = select_client_resource(ua); - if (!client) { - return 1; + case 1: /* jobs */ + client = get_client_resource(ua); + if (client) { + purge_jobs_from_client(ua, client); + } + break; + case 2: /* Volume */ + if (select_media_dbr(ua, &mr)) { + purge_jobs_from_volume(ua, &mr); } - purge_jobs_from_client(ua, client); break; } return 1; } /* - * Prune File records from the database. For any Job which + * Purge File records from the database. For any Job which * is older than the retention period, we unconditionally delete * all File records for that Job. This is simple enough that no * temporary tables are needed. We simply make an in memory list of * the JobIds meeting the prune conditions, then delete all File records * pointing to each of those JobIds. */ -int purge_files_from_client(UAContext *ua, CLIENT *client) +static int purge_files_from_client(UAContext *ua, CLIENT *client) { - struct s_file_del_ctx del; - char *query = (char *)get_pool_memory(PM_MESSAGE); - int i; + struct del_ctx del; + POOL_MEM query(PM_MESSAGE); CLIENT_DBR cr; + char ed1[50]; memset(&cr, 0, sizeof(cr)); - memset(&del, 0, sizeof(del)); - - strcpy(cr.Name, client->hdr.name); - if (!db_create_client_record(ua->db, &cr)) { + bstrncpy(cr.Name, client->name(), sizeof(cr.Name)); + if (!db_create_client_record(ua->jcr, ua->db, &cr)) { return 0; } - Mmsg(&query, select_jobsfiles_from_client, cr.ClientId); + memset(&del, 0, sizeof(del)); + del.max_ids = 1000; + del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids); - Dmsg1(050, "select sql=%s\n", query); - - if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) { - bsendmsg(ua, "%s", db_strerror(ua->db)); - Dmsg0(050, "Count failed\n"); - goto bail_out; - } - - if (del.tot_ids == 0) { - bsendmsg(ua, _("No Files found for client %s to purge from %s catalog.\n"), - client->hdr.name, client->catalog->hdr.name); - goto bail_out; - } + ua->info_msg(_("Begin purging files for Client \"%s\"\n"), cr.Name); - if (del.tot_ids < MAX_DEL_LIST_LEN) { - del.max_ids = del.tot_ids + 1; - } else { - del.max_ids = MAX_DEL_LIST_LEN; - } - del.tot_ids = 0; + Mmsg(query, select_jobsfiles_from_client, edit_int64(cr.ClientId, ed1)); + Dmsg1(050, "select sql=%s\n", query.c_str()); + db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del); - del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids); + purge_files_from_job_list(ua, del); - db_sql_query(ua->db, query, file_delete_handler, (void *)&del); - - for (i=0; i < del.num_ids; i++) { - Dmsg1(050, "Delete JobId=%d\n", del.JobId[i]); - Mmsg(&query, "DELETE FROM File WHERE JobId=%d", del.JobId[i]); - db_sql_query(ua->db, query, NULL, (void *)NULL); - /* - * Now mark Job as having files purged. This is necessary to - * avoid having too many Jobs to process in future prunings. If - * we don't do this, the number of JobId's in our in memory list - * will grow very large. - */ - Mmsg(&query, "UPDATE Job Set PurgedFiles=1 WHERE JobId=%d", del.JobId[i]); - db_sql_query(ua->db, query, NULL, (void *)NULL); - Dmsg1(050, "Del sql=%s\n", query); + if (del.num_ids == 0) { + ua->warning_msg(_("No Files found for client %s to purge from %s catalog.\n"), + client->name(), client->catalog->name()); + } else { + ua->info_msg(_("Files for %d Jobs for client \"%s\" purged from %s catalog.\n"), del.num_ids, + client->name(), client->catalog->name()); } - bsendmsg(ua, _("%d Files for client %s purged from %s catalog.\n"), del.num_ids, - client->hdr.name, client->catalog->hdr.name); - -bail_out: + if (del.JobId) { free(del.JobId); } - free_pool_memory(query); return 1; } /* - * Purging Jobs is a bit more complicated than purging Files - * because we delete Job records only if there is a more current - * backup of the FileSet. Otherwise, we keep the Job record. - * In other words, we never delete the only Job record that - * contains a current backup of a FileSet. This prevents the - * Volume from being recycled and destroying a current backup. + * Purge Job records from the database. For any Job which + * is older than the retention period, we unconditionally delete + * it and all File records for that Job. This is simple enough that no + * temporary tables are needed. We simply make an in memory list of + * the JobIds then delete the Job, Files, and JobMedia records in that list. */ -int purge_jobs_from_client(UAContext *ua, CLIENT *client) +static int purge_jobs_from_client(UAContext *ua, CLIENT *client) { - struct s_job_del_ctx del; - char *query = (char *)get_pool_memory(PM_MESSAGE); - int i; + struct del_ctx del; + POOL_MEM query(PM_MESSAGE); CLIENT_DBR cr; + char ed1[50]; memset(&cr, 0, sizeof(cr)); - memset(&del, 0, sizeof(del)); - strcpy(cr.Name, client->hdr.name); - if (!db_create_client_record(ua->db, &cr)) { + bstrncpy(cr.Name, client->name(), sizeof(cr.Name)); + if (!db_create_client_record(ua->jcr, ua->db, &cr)) { return 0; } - Mmsg(&query, select_jobs_from_client, cr.ClientId); - - Dmsg1(050, "select sql=%s\n", query); - - if (!db_sql_query(ua->db, query, job_count_handler, (void *)&del)) { - bsendmsg(ua, "%s", db_strerror(ua->db)); - Dmsg0(050, "Count failed\n"); - goto bail_out; - } - if (del.tot_ids == 0) { - bsendmsg(ua, _("No Jobs found for client %s to purge from %s catalog.\n"), - client->hdr.name, client->catalog->hdr.name); - goto bail_out; - } - - if (del.tot_ids < MAX_DEL_LIST_LEN) { - del.max_ids = del.tot_ids + 1; - } else { - del.max_ids = MAX_DEL_LIST_LEN; - } - - del.tot_ids = 0; - + memset(&del, 0, sizeof(del)); + del.max_ids = 1000; del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids); del.PurgedFiles = (char *)malloc(del.max_ids); + + ua->info_msg(_("Begin purging jobs from Client \"%s\"\n"), cr.Name); - db_sql_query(ua->db, query, job_delete_handler, (void *)&del); - - /* - * OK, now we have the list of JobId's to be purged, first check - * if the Files have been purged, if not, purge (delete) them. - * Then delete the Job entry, and finally and JobMedia records. - */ - for (i=0; i < del.num_ids; i++) { - Dmsg1(050, "Delete JobId=%d\n", del.JobId[i]); - if (!del.PurgedFiles[i]) { - Mmsg(&query, "DELETE FROM File WHERE JobId=%d", del.JobId[i]); - db_sql_query(ua->db, query, NULL, (void *)NULL); - Dmsg1(050, "Del sql=%s\n", query); - } + Mmsg(query, select_jobs_from_client, edit_int64(cr.ClientId, ed1)); + Dmsg1(150, "select sql=%s\n", query.c_str()); + db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del); - Mmsg(&query, "DELETE FROM Job WHERE JobId=%d", del.JobId[i]); - db_sql_query(ua->db, query, NULL, (void *)NULL); - Dmsg1(050, "Del sql=%s\n", query); + purge_job_list_from_catalog(ua, del); - Mmsg(&query, "DELETE FROM JobMedia WHERE JobId=%d", del.JobId[i]); - db_sql_query(ua->db, query, NULL, (void *)NULL); - Dmsg1(050, "Del sql=%s\n", query); + if (del.num_ids == 0) { + ua->warning_msg(_("No Files found for client %s to purge from %s catalog.\n"), + client->name(), client->catalog->name()); + } else { + ua->info_msg(_("%d Jobs for client %s purged from %s catalog.\n"), del.num_ids, + client->name(), client->catalog->name()); } - bsendmsg(ua, _("%d Jobs for client %s purged from %s catalog.\n"), del.num_ids, - client->hdr.name, client->catalog->hdr.name); - -bail_out: + if (del.JobId) { free(del.JobId); } if (del.PurgedFiles) { free(del.PurgedFiles); } - free_pool_memory(query); return 1; } -void purge_files_from_job(UAContext *ua, JOB_DBR *jr) + +/* + * Remove File records from a list of JobIds + */ +void purge_files_from_jobs(UAContext *ua, char *jobs) { - char *query = (char *)get_pool_memory(PM_MESSAGE); - - Mmsg(&query, "DELETE FROM File WHERE JobId=%d", jr->JobId); - db_sql_query(ua->db, query, NULL, (void *)NULL); + POOL_MEM query(PM_MESSAGE); - Mmsg(&query, "UPDATE Job Set PurgedFiles=1 WHERE JobId=%d", jr->JobId); - db_sql_query(ua->db, query, NULL, (void *)NULL); + Mmsg(query, "DELETE FROM File WHERE JobId IN (%s)", jobs); + db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL); + Dmsg1(050, "Delete File sql=%s\n", query.c_str()); - free_pool_memory(query); + /* + * Now mark Job as having files purged. This is necessary to + * avoid having too many Jobs to process in future prunings. If + * we don't do this, the number of JobId's in our in memory list + * could grow very large. + */ + Mmsg(query, "UPDATE Job SET PurgedFiles=1 WHERE JobId IN (%s)", jobs); + db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL); + Dmsg1(050, "Mark purged sql=%s\n", query.c_str()); } -void purge_files_from_volume(UAContext *ua, POOL_DBR *pr, MEDIA_DBR *mr ) +/* + * Delete jobs (all records) from the catalog in groups of 1000 + * at a time. + */ +void purge_job_list_from_catalog(UAContext *ua, del_ctx &del) +{ + POOL_MEM jobids(PM_MESSAGE); + char ed1[50]; + + for (int i=0; del.num_ids; ) { + Dmsg1(150, "num_ids=%d\n", del.num_ids); + pm_strcat(jobids, ""); + for (int j=0; j<1000 && del.num_ids>0; j++) { + del.num_ids--; + if (del.JobId[i] == 0 || ua->jcr->JobId == del.JobId[i]) { + Dmsg2(150, "skip JobId[%d]=%d\n", i, (int)del.JobId[i]); + i++; + continue; + } + if (*jobids.c_str() != 0) { + pm_strcat(jobids, ","); + } + pm_strcat(jobids, edit_int64(del.JobId[i++], ed1)); + Dmsg1(150, "Add id=%s\n", ed1); + del.num_del++; + } + Dmsg1(150, "num_ids=%d\n", del.num_ids); + purge_jobs_from_catalog(ua, jobids.c_str()); + } +} + +/* + * Delete files from a list of jobs in groups of 1000 + * at a time. + */ +void purge_files_from_job_list(UAContext *ua, del_ctx &del) +{ + POOL_MEM jobids(PM_MESSAGE); + char ed1[50]; + /* + * OK, now we have the list of JobId's to be pruned, send them + * off to be deleted batched 1000 at a time. + */ + for (int i=0; del.num_ids; ) { + pm_strcat(jobids, ""); + for (int j=0; j<1000 && del.num_ids>0; j++) { + del.num_ids--; + if (del.JobId[i] == 0 || ua->jcr->JobId == del.JobId[i]) { + Dmsg2(150, "skip JobId[%d]=%d\n", i, (int)del.JobId[i]); + i++; + continue; + } + if (*jobids.c_str() != 0) { + pm_strcat(jobids, ","); + } + pm_strcat(jobids, edit_int64(del.JobId[i++], ed1)); + Dmsg1(150, "Add id=%s\n", ed1); + del.num_del++; + } + purge_files_from_jobs(ua, jobids.c_str()); + } +} + +/* + * Remove all records from catalog for a list of JobIds + */ +void purge_jobs_from_catalog(UAContext *ua, char *jobs) +{ + POOL_MEM query(PM_MESSAGE); + + /* Delete (or purge) records associated with the job */ + purge_files_from_jobs(ua, jobs); + + Mmsg(query, "DELETE FROM JobMedia WHERE JobId IN (%s)", jobs); + db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL); + Dmsg1(050, "Delete JobMedia sql=%s\n", query.c_str()); + + Mmsg(query, "DELETE FROM Log WHERE JobId IN (%s)", jobs); + db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL); + Dmsg1(050, "Delete Log sql=%s\n", query.c_str()); + + /* Now remove the Job record itself */ + Mmsg(query, "DELETE FROM Job WHERE JobId IN (%s)", jobs); + db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL); + Dmsg1(050, "Delete Job sql=%s\n", query.c_str()); +} + + +void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr ) {} /* ***FIXME*** implement */ -void purge_jobs_from_volume(UAContext *ua, POOL_DBR *pr, MEDIA_DBR *mr) +/* + * Returns: 1 if Volume purged + * 0 if Volume not purged + */ +bool purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr) { - char *query = (char *)get_pool_memory(PM_MESSAGE); - struct s_count_ctx cnt; - struct s_file_del_ctx del; + POOL_MEM query(PM_MESSAGE); + struct del_ctx del; int i; + bool purged = false; + bool stat; JOB_DBR jr; + char ed1[50]; + + stat = strcmp(mr->VolStatus, "Append") == 0 || + strcmp(mr->VolStatus, "Full") == 0 || + strcmp(mr->VolStatus, "Used") == 0 || + strcmp(mr->VolStatus, "Error") == 0; + if (!stat) { + ua->error_msg(_("\nVolume \"%s\" has VolStatus \"%s\" and cannot be purged.\n" + "The VolStatus must be: Append, Full, Used, or Error to be purged.\n"), + mr->VolumeName, mr->VolStatus); + return 0; + } memset(&jr, 0, sizeof(jr)); memset(&del, 0, sizeof(del)); - cnt.count = 0; - Mmsg(&query, "SELECT count(*) FROM JobMedia WHERE MediaId=%d", mr->MediaId); - if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) { - bsendmsg(ua, "%s", db_strerror(ua->db)); - Dmsg0(050, "Count failed\n"); - goto bail_out; - } - - if (cnt.count == 0) { - bsendmsg(ua, "There are no Jobs associated with Volume %s. Marking it purged.\n", - mr->VolumeName); - if (!mark_media_purged(ua, mr)) { - goto bail_out; - } - goto bail_out; - } + del.max_ids = 1000; + del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids); - if (cnt.count < MAX_DEL_LIST_LEN) { - del.max_ids = cnt.count + 1; + /* + * Check if he wants to purge a single jobid + */ + i = find_arg_with_value(ua, "jobid"); + if (i >= 0) { + del.num_ids = 1; + del.JobId[0] = str_to_int64(ua->argv[i]); } else { - del.max_ids = MAX_DEL_LIST_LEN; + /* + * Purge ALL JobIds + */ + Mmsg(query, "SELECT DISTINCT JobId FROM JobMedia WHERE MediaId=%s", + edit_int64(mr->MediaId, ed1)); + if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del)) { + ua->error_msg("%s", db_strerror(ua->db)); + Dmsg0(050, "Count failed\n"); + goto bail_out; + } } - del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids); + purge_job_list_from_catalog(ua, del); - Mmsg(&query, "SELECT JobId FROM JobMedia WHERE MediaId=%d", mr->MediaId); - if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) { - bsendmsg(ua, "%s", db_strerror(ua->db)); - Dmsg0(050, "Count failed\n"); - goto bail_out; - } + ua->info_msg(_("%d File%s on Volume \"%s\" purged from catalog.\n"), del.num_del, + del.num_del==1?"":"s", mr->VolumeName); - for (i=0; i < del.num_ids; i++) { - Dmsg1(050, "Delete JobId=%d\n", del.JobId[i]); - Mmsg(&query, "DELETE FROM File WHERE JobId=%d", del.JobId[i]); - db_sql_query(ua->db, query, NULL, (void *)NULL); - Mmsg(&query, "DELETE FROM Job WHERE JobId=%d", del.JobId[i]); - db_sql_query(ua->db, query, NULL, (void *)NULL); - Mmsg(&query, "DELETE FROM JobMedia WHERE JobId=%d", del.JobId[i]); - db_sql_query(ua->db, query, NULL, (void *)NULL); - Dmsg1(050, "Del sql=%s\n", query); - del.num_del++; - } + purged = is_volume_purged(ua, mr); + +bail_out: if (del.JobId) { free(del.JobId); } - bsendmsg(ua, _("%d Files for Volume %s purged from catalog.\n"), del.num_del, - mr->VolumeName); + return purged; +} +/* + * This routine will check the JobMedia records to see if the + * Volume has been purged. If so, it marks it as such and + * + * Returns: true if volume purged + * false if not + */ +bool is_volume_purged(UAContext *ua, MEDIA_DBR *mr) +{ + POOL_MEM query(PM_MESSAGE); + struct s_count_ctx cnt; + bool purged = false; + char ed1[50]; + + if (strcmp(mr->VolStatus, "Purged") == 0) { + purged = true; + goto bail_out; + } /* If purged, mark it so */ - if (del.num_ids == del.num_del) { - mark_media_purged(ua, mr); + cnt.count = 0; + Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%s", + edit_int64(mr->MediaId, ed1)); + if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) { + ua->error_msg("%s", db_strerror(ua->db)); + Dmsg0(050, "Count failed\n"); + goto bail_out; } -bail_out: - free_pool_memory(query); + if (cnt.count == 0) { + ua->warning_msg(_("There are no more Jobs associated with Volume \"%s\". Marking it purged.\n"), + mr->VolumeName); + if (!(purged = mark_media_purged(ua, mr))) { + ua->error_msg("%s", db_strerror(ua->db)); + } + } +bail_out: + return purged; } /* - * IF volume status is Append, Full, or Used, mark it Purged + * IF volume status is Append, Full, Used, or Error, mark it Purged * Purged volumes can then be recycled (if enabled). */ -int mark_media_purged(UAContext *ua, MEDIA_DBR *mr) +bool mark_media_purged(UAContext *ua, MEDIA_DBR *mr) { - if (strcmp(mr->VolStatus, "Append") == 0 || + JCR *jcr = ua->jcr; + if (strcmp(mr->VolStatus, "Append") == 0 || strcmp(mr->VolStatus, "Full") == 0 || - strcmp(mr->VolStatus, "Used") == 0) { - strcpy(mr->VolStatus, "Purged"); - if (!db_update_media_record(ua->db, mr)) { - if (ua->verbose) { - bsendmsg(ua, "%s", db_strerror(ua->db)); - } - return 0; + strcmp(mr->VolStatus, "Used") == 0 || + strcmp(mr->VolStatus, "Error") == 0) { + bstrncpy(mr->VolStatus, "Purged", sizeof(mr->VolStatus)); + if (!db_update_media_record(jcr, ua->db, mr)) { + return false; } - return 1; + pm_strcpy(jcr->VolumeName, mr->VolumeName); + generate_job_event(jcr, "VolumePurged"); + /* + * If the RecyclePool is defined, move the volume there + */ + if (mr->RecyclePoolId && mr->RecyclePoolId != mr->PoolId) { + POOL_DBR oldpr, newpr; + memset(&oldpr, 0, sizeof(POOL_DBR)); + memset(&newpr, 0, sizeof(POOL_DBR)); + newpr.PoolId = mr->RecyclePoolId; + oldpr.PoolId = mr->PoolId; + if ( db_get_pool_record(jcr, ua->db, &oldpr) + && db_get_pool_record(jcr, ua->db, &newpr)) + { + /* check if destination pool size is ok */ + if (newpr.MaxVols > 0 && newpr.NumVols >= newpr.MaxVols) { + ua->error_msg(_("Unable move recycled Volume in full " + "Pool \"%s\" MaxVols=%d\n"), + newpr.Name, newpr.MaxVols); + + } else { /* move media */ + update_vol_pool(ua, newpr.Name, mr, &oldpr); + } + } else { + ua->error_msg("%s", db_strerror(ua->db)); + } + } + /* Send message to Job report, if it is a *real* job */ + if (jcr && jcr->JobId > 0) { + Jmsg(jcr, M_INFO, 0, _("All records pruned from Volume \"%s\"; marking it \"Purged\"\n"), + mr->VolumeName); + } + return true; + } else { + ua->error_msg(_("Cannot purge Volume with VolStatus=%s\n"), mr->VolStatus); } - return strcpy(mr->VolStatus, "Purged") == 0; + return strcmp(mr->VolStatus, "Purged") == 0; }