/*
Bacula® - The Network Backup Solution
- Copyright (C) 2002-2008 Free Software Foundation Europe e.V.
+ Copyright (C) 2002-2010 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
+ modify it under the terms of version three of the GNU Affero General Public
License as published by the Free Software Foundation and included
in the file LICENSE.
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
+ You should have received a copy of the GNU Affero 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.
*
* Kern Sibbald, February MMII
*
- * Version $Id$
*/
#include "bacula.h"
/* Forward referenced functions */
static int purge_files_from_client(UAContext *ua, CLIENT *client);
static int purge_jobs_from_client(UAContext *ua, CLIENT *client);
+static int action_on_purge_cmd(UAContext *ua, const char *cmd);
static const char *select_jobsfiles_from_client =
"SELECT JobId FROM Job "
}
/* Volume */
case 2:
+ /* Perform ActionOnPurge (action=truncate) */
+ if (find_arg(ua, "action") >= 0) {
+ return action_on_purge_cmd(ua, ua->cmd);
+ }
+
while ((i=find_arg(ua, NT_("volume"))) >= 0) {
if (select_media_dbr(ua, &mr)) {
purge_jobs_from_volume(ua, &mr, /*force*/true);
POOL_MEM query(PM_MESSAGE);
db_lock(ua->db);
+
/* Do it in two times for mysql */
- Mmsg(query, "CREATE TEMPORARY TABLE cpy_tmp AS "
- "SELECT MIN(JobId) AS JobId FROM Job " /* Choose the oldest job */
- "WHERE Type='%c' "
- "AND ( PriorJobId IN (%s) "
- "OR "
- " PriorJobId IN ( "
- "SELECT PriorJobId "
- "FROM Job "
- "WHERE JobId IN (%s) "
- " AND Type='B' "
- ") "
- ") "
- "GROUP BY PriorJobId ", /* one result per copy */
- JT_JOB_COPY, jobs, jobs);
+ Mmsg(query, uap_upgrade_copies_oldest_job[db_get_type_index(ua->db)], JT_JOB_COPY, jobs, jobs);
db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
Dmsg1(050, "Upgrade copies Log sql=%s\n", query.c_str());
/* Now upgrade first copy to Backup */
- Mmsg(query, "UPDATE Job SET Type='B' " /* JT_JOB_COPY => JT_BACKUP */
+ Mmsg(query, "UPDATE Job SET Type='B' " /* JT_JOB_COPY => JT_BACKUP */
"WHERE JobId IN ( SELECT JobId FROM cpy_tmp )");
db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
Dmsg1(050, "Delete Log sql=%s\n", query.c_str());
+ Mmsg(query, "DELETE FROM RestoreObject WHERE JobId IN (%s)", jobs);
+ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
+ Dmsg1(050, "Delete RestoreObject sql=%s\n", query.c_str());
+
+ Mmsg(query, "DELETE FROM PathVisibility WHERE JobId IN (%s)", jobs);
+ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
+ Dmsg1(050, "Delete PathVisibility sql=%s\n", query.c_str());
+
upgrade_copies(ua, jobs);
/* Now remove the Job record itself */
bool purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr, bool force)
{
POOL_MEM query(PM_MESSAGE);
- struct del_ctx del;
+ db_list_ctx lst;
+ char *jobids=NULL;
int i;
bool purged = false;
bool stat;
- JOB_DBR jr;
- char ed1[50];
stat = strcmp(mr->VolStatus, "Append") == 0 ||
strcmp(mr->VolStatus, "Full") == 0 ||
return 0;
}
- memset(&jr, 0, sizeof(jr));
- memset(&del, 0, sizeof(del));
- del.max_ids = 1000;
- del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
-
/*
* 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]);
+ if (i >= 0 && is_a_number_list(ua->argv[i])) {
+ jobids = ua->argv[i];
} else {
/*
* 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)) {
+ if (!db_get_volume_jobids(ua->jcr, ua->db, mr, &lst)) {
ua->error_msg("%s", db_strerror(ua->db));
Dmsg0(050, "Count failed\n");
goto bail_out;
}
+ jobids = lst.list;
}
- purge_job_list_from_catalog(ua, del);
+ if (*jobids) {
+ purge_jobs_from_catalog(ua, jobids);
+ }
- ua->info_msg(_("%d File%s on Volume \"%s\" purged from catalog.\n"), del.num_del,
- del.num_del==1?"":"s", mr->VolumeName);
+ ua->info_msg(_("%d File%s on Volume \"%s\" purged from catalog.\n"),
+ lst.count, lst.count<=1?"":"s", mr->VolumeName);
purged = is_volume_purged(ua, mr, force);
bail_out:
- if (del.JobId) {
- free(del.JobId);
- }
return purged;
}
/* If purged, mark it so */
cnt.count = 0;
- Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%s",
+ Mmsg(query, "SELECT 1 FROM JobMedia WHERE MediaId=%s LIMIT 1",
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));
return ua->jcr->store_bsock;
}
-static void do_actions_on_purge(UAContext *ua, MEDIA_DBR *mr)
+/*
+ * Called here to send the appropriate commands to the SD
+ * to do truncate on purge.
+ */
+static void do_truncate_on_purge(UAContext *ua, MEDIA_DBR *mr,
+ char *pool, char *storage,
+ int drive, BSOCK *sd)
{
- BSOCK *sd;
- POOL_DBR pr;
- bool ok=false;
int dvd;
+ bool ok=false;
uint64_t VolBytes = 0;
- char dev_name[MAX_NAME_LENGTH];
-
- if (mr->ActionOnPurge & AOP_TRUNCATE) {
- /* Send the command to truncate the volume after purge. If this feature
- * is disabled for the specific device, this will be a no-op.
- */
+
+ /* TODO: Return if not mr->Recyle ? */
+ if (!mr->Recycle) {
+ return;
+ }
- if ((sd=open_sd_bsock(ua)) != NULL) {
- memset(&pr, 0, sizeof(POOL_DBR));
-
- pr.PoolId = mr->PoolId;
- strcpy(pr.Name, "Default"); /* We don't use the Pool in label */
- bstrncpy(dev_name, ua->jcr->wstore->dev_name(), sizeof(dev_name));
-
- /* Protect us from spaces */
- bash_spaces(dev_name);
- bash_spaces(mr->VolumeName);
- bash_spaces(mr->MediaType);
- bash_spaces(pr.Name);
-
- /* We set drive=-1 to let the storage decide of which drive
- * to use
- */
- sd->fsend("relabel %s OldName=%s NewName=%s PoolName=%s "
- "MediaType=%s Slot=%d drive=%d\n",
- dev_name,
- mr->VolumeName, mr->VolumeName,
- pr.Name, mr->MediaType, mr->Slot, -1);
-
- unbash_spaces(mr->VolumeName);
- unbash_spaces(mr->MediaType);
- while (sd->recv() >= 0) {
- ua->send_msg("%s", sd->msg);
- if (sscanf(sd->msg, "3000 OK label. VolBytes=%llu DVD=%d ",
- &VolBytes, &dvd) == 2)
- {
- ok = true;
- }
- }
- sd->signal(BNET_TERMINATE);
- sd->close();
- ua->jcr->store_bsock = NULL;
+ /* Do it only if action on purge = truncate is set */
+ if (!(mr->ActionOnPurge & ON_PURGE_TRUNCATE)) {
+ return;
+ }
+ /*
+ * Send the command to truncate the volume after purge. If this feature
+ * is disabled for the specific device, this will be a no-op.
+ */
+
+ /* Protect us from spaces */
+ bash_spaces(mr->VolumeName);
+ bash_spaces(mr->MediaType);
+ bash_spaces(pool);
+ bash_spaces(storage);
+
+ /* Do it by relabeling the Volume, which truncates it */
+ sd->fsend("relabel %s OldName=%s NewName=%s PoolName=%s "
+ "MediaType=%s Slot=%d drive=%d\n",
+ storage,
+ mr->VolumeName, mr->VolumeName,
+ pool, mr->MediaType, mr->Slot, drive);
+
+ unbash_spaces(mr->VolumeName);
+ unbash_spaces(mr->MediaType);
+ unbash_spaces(pool);
+ unbash_spaces(storage);
+
+ /* Send relabel command, and check for valid response */
+ while (sd->recv() >= 0) {
+ ua->send_msg("%s", sd->msg);
+ if (sscanf(sd->msg, "3000 OK label. VolBytes=%llu DVD=%d ", &VolBytes, &dvd) == 2) {
+ ok = true;
+ }
+ }
+
+ if (ok) {
+ mr->VolBytes = VolBytes;
+ mr->VolFiles = 0;
+ if (!db_update_media_record(ua->jcr, ua->db, mr)) {
+ ua->error_msg(_("Can't update volume size in the catalog\n"));
+ }
+ ua->send_msg(_("The volume \"%s\" has been truncated\n"), mr->VolumeName);
+ } else {
+ ua->warning_msg(_("Unable to truncate volume \"%s\"\n"), mr->VolumeName);
+ }
+}
+
+/*
+ * Implement Bacula bconsole command purge action
+ * purge action= pool= volume= storage= devicetype=
+ */
+static int action_on_purge_cmd(UAContext *ua, const char *cmd)
+{
+ bool allpools = false;
+ int drive = -1;
+ int nb = 0;
+ uint32_t *results = NULL;
+ const char *action = "all";
+ STORE *store = NULL;
+ POOL *pool = NULL;
+ MEDIA_DBR mr;
+ POOL_DBR pr;
+ BSOCK *sd = NULL;
+
+ memset(&pr, 0, sizeof(pr));
+ memset(&mr, 0, sizeof(mr));
+
+ /* Look at arguments */
+ for (int i=1; i<ua->argc; i++) {
+ if (strcasecmp(ua->argk[i], NT_("allpools")) == 0) {
+ allpools = true;
+
+ } else if (strcasecmp(ua->argk[i], NT_("volume")) == 0
+ && is_name_valid(ua->argv[i], NULL)) {
+ bstrncpy(mr.VolumeName, ua->argv[i], sizeof(mr.VolumeName));
+
+ } else if (strcasecmp(ua->argk[i], NT_("devicetype")) == 0
+ && ua->argv[i]) {
+ bstrncpy(mr.MediaType, ua->argv[i], sizeof(mr.MediaType));
- } else {
- ua->error_msg(_("Could not connect to storage daemon"));
+ } else if (strcasecmp(ua->argk[i], NT_("drive")) == 0 && ua->argv[i]) {
+ drive = atoi(ua->argv[i]);
+
+ } else if (strcasecmp(ua->argk[i], NT_("action")) == 0
+ && is_name_valid(ua->argv[i], NULL)) {
+ action=ua->argv[i];
}
-
- if (ok) {
- mr->VolBytes = VolBytes;
- mr->VolFiles = 0;
- if (!db_update_media_record(ua->jcr, ua->db, mr)) {
- ua->error_msg(_("Can't update volume size in the catalog\n"));
+ }
+
+ /* Choose storage */
+ ua->jcr->wstore = store = get_storage_resource(ua, false);
+ if (!store) {
+ goto bail_out;
+ }
+ mr.StorageId = store->StorageId;
+
+ if (!open_db(ua)) {
+ Dmsg0(100, "Can't open db\n");
+ goto bail_out;
+ }
+
+ if (!allpools) {
+ /* force pool selection */
+ pool = get_pool_resource(ua);
+ if (!pool) {
+ Dmsg0(100, "Can't get pool resource\n");
+ goto bail_out;
+ }
+ bstrncpy(pr.Name, pool->name(), sizeof(pr.Name));
+ if (!db_get_pool_record(ua->jcr, ua->db, &pr)) {
+ Dmsg0(100, "Can't get pool record\n");
+ goto bail_out;
+ }
+ mr.PoolId = pr.PoolId;
+ }
+
+ /*
+ * Look for all Purged volumes that can be recycled, are enabled and
+ * have more the 10,000 bytes.
+ */
+ mr.Recycle = 1;
+ mr.Enabled = 1;
+ mr.VolBytes = 10000;
+ bstrncpy(mr.VolStatus, "Purged", sizeof(mr.VolStatus));
+ if (!db_get_media_ids(ua->jcr, ua->db, &mr, &nb, &results)) {
+ Dmsg0(100, "No results from db_get_media_ids\n");
+ goto bail_out;
+ }
+
+ if (!nb) {
+ ua->send_msg(_("No Volumes found to perform %s action.\n"), action);
+ goto bail_out;
+ }
+
+ if ((sd=open_sd_bsock(ua)) == NULL) {
+ Dmsg0(100, "Can't open connection to sd\n");
+ goto bail_out;
+ }
+
+ /*
+ * Loop over the candidate Volumes and actually truncate them
+ */
+ for (int i=0; i < nb; i++) {
+ memset(&mr, 0, sizeof(mr));
+ mr.MediaId = results[i];
+ if (db_get_media_record(ua->jcr, ua->db, &mr)) {
+ /* TODO: ask for drive and change Pool */
+ if (!strcasecmp("truncate", action) || !strcasecmp("all", action)) {
+ do_truncate_on_purge(ua, &mr, pr.Name, store->dev_name(), drive, sd);
}
- ua->send_msg(_("The volume has been truncated\n"));
} else {
- ua->warning_msg(_("Unable to truncate the volume\n"));
+ Dmsg1(0, "Can't find MediaId=%lld\n", (uint64_t) mr.MediaId);
}
}
+
+bail_out:
+ close_db(ua);
+ if (sd) {
+ sd->signal(BNET_TERMINATE);
+ sd->close();
+ ua->jcr->store_bsock = NULL;
+ }
+ ua->jcr->wstore = NULL;
+ if (results) {
+ free(results);
+ }
+
+ return 1;
}
/*
ua->error_msg("%s", db_strerror(ua->db));
}
}
-
- /* Do any ActionOnPurge for this Volume */
- do_actions_on_purge(ua, mr);
/* Send message to Job report, if it is a *real* job */
if (jcr && jcr->JobId > 0) {