]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/dird/ua_purge.c
Fix problem in BVFS with concurrent queries
[bacula/bacula] / bacula / src / dird / ua_purge.c
index a8209a26d0df1e8324f9f30cf9b1524640ae52bd..550ab37c383c3e9bf1895a76b0d10c8c9cd6f0d7 100644 (file)
@@ -1,26 +1,26 @@
 /*
    Bacula® - The Network Backup Solution
 
-   Copyright (C) 2002-2007 Free Software Foundation Europe e.V.
+   Copyright (C) 2002-2012 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 plus additions
-   that are listed in the file LICENSE.
+   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.
 
    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
+   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.
 
-   Bacula® is a registered trademark of John Walker.
+   Bacula® is a registered trademark of Kern Sibbald.
    The licensor of Bacula is the Free Software Foundation Europe
    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
    Switzerland, email:ftf@fsfeurope.org.
@@ -35,7 +35,6 @@
  *
  *     Kern Sibbald, February MMII
  *
- *   Version $Id$
  */
 
 #include "bacula.h"
@@ -44,6 +43,7 @@
 /* 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 "
@@ -87,11 +87,11 @@ int purgecmd(UAContext *ua, const char *cmd)
       NULL};
 
    ua->warning_msg(_(
-      "\nThis command is can be DANGEROUS!!!\n\n"
+      "\nThis command 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 without regard\n"
-      "for retention periods. Normally you should use the\n"
+      "to retention periods. Normally you should use the\n"
       "PRUNE command, which respects retention periods.\n"));
 
    if (!open_db(ua)) {
@@ -132,15 +132,20 @@ int purgecmd(UAContext *ua, const char *cmd)
          return 1;
       case 1:                         /* Volume */
          if (select_media_dbr(ua, &mr)) {
-            purge_jobs_from_volume(ua, &mr);
+            purge_jobs_from_volume(ua, &mr, /*force*/true);
          }
          return 1;
       }
    /* 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);
+            purge_jobs_from_volume(ua, &mr, /*force*/true);
          }
          *ua->argk[i] = 0;            /* zap keyword already seen */
          ua->send_msg("\n");
@@ -164,7 +169,7 @@ int purgecmd(UAContext *ua, const char *cmd)
       break;
    case 2:                            /* Volume */
       if (select_media_dbr(ua, &mr)) {
-         purge_jobs_from_volume(ua, &mr);
+         purge_jobs_from_volume(ua, &mr, /*force*/true);
       }
       break;
    }
@@ -283,6 +288,10 @@ void purge_files_from_jobs(UAContext *ua, char *jobs)
    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
    Dmsg1(050, "Delete File sql=%s\n", query.c_str());
 
+   Mmsg(query, "DELETE FROM BaseFiles WHERE JobId IN (%s)", jobs);
+   db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
+   Dmsg1(050, "Delete BaseFiles sql=%s\n", query.c_str());
+
    /*
     * Now mark Job as having files purged. This is necessary to
     * avoid having too many Jobs to process in future prunings. If
@@ -357,6 +366,45 @@ void purge_files_from_job_list(UAContext *ua, del_ctx &del)
    }
 }
 
+/*
+ * Change the type of the next copy job to backup.
+ * We need to upgrade the next copy of a normal job,
+ * and also upgrade the next copy when the normal job
+ * already have been purged.
+ *
+ *   JobId: 1   PriorJobId: 0    (original)
+ *   JobId: 2   PriorJobId: 1    (first copy)
+ *   JobId: 3   PriorJobId: 1    (second copy)
+ *
+ *   JobId: 2   PriorJobId: 1    (first copy, now regular backup)
+ *   JobId: 3   PriorJobId: 1    (second copy)
+ *
+ *  => Search through PriorJobId in jobid and
+ *                    PriorJobId in PriorJobId (jobid)
+ */
+void upgrade_copies(UAContext *ua, char *jobs)
+{
+   POOL_MEM query(PM_MESSAGE);
+   
+   db_lock(ua->db);
+
+   /* Do it in two times for mysql */
+   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  */
+                "WHERE JobId IN ( SELECT JobId FROM cpy_tmp )");
+
+   db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
+
+   Mmsg(query, "DROP TABLE cpy_tmp");
+   db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
+
+   db_unlock(ua->db);
+}
+
 /*
  * Remove all records from catalog for a list of JobIds
  */
@@ -375,13 +423,23 @@ void purge_jobs_from_catalog(UAContext *ua, char *jobs)
    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 */
    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 */
 
@@ -389,15 +447,14 @@ void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr )
  * Returns: 1 if Volume purged
  *          0 if Volume not purged
  */
-bool purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr)
+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 ||
@@ -410,42 +467,34 @@ bool purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr)
       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);
+   purged = is_volume_purged(ua, mr, force); 
 
 bail_out:
-   if (del.JobId) {
-      free(del.JobId);
-   }
    return purged;
 }
 
@@ -455,21 +504,32 @@ bail_out:
  *
  * Returns: true if volume purged
  *          false if not
+ *
+ * Note, we normally will not purge a volume that has Firstor LastWritten
+ *   zero, because it means the volume is most likely being written
+ *   however, if the user manually purges using the purge command in
+ *   the console, he has been warned, and we go ahead and purge
+ *   the volume anyway, if possible).
  */
-bool is_volume_purged(UAContext *ua, MEDIA_DBR *mr)
+bool is_volume_purged(UAContext *ua, MEDIA_DBR *mr, bool force)
 {
    POOL_MEM query(PM_MESSAGE);
    struct s_count_ctx cnt;
    bool purged = false;
    char ed1[50];
 
+   if (!force && (mr->FirstWritten == 0 || mr->LastWritten == 0)) {
+      goto bail_out;               /* not written cannot purge */
+   }
+
    if (strcmp(mr->VolStatus, "Purged") == 0) {
       purged = true;
       goto bail_out;
    }
+
    /* 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));
@@ -488,6 +548,208 @@ bail_out:
    return purged;
 }
 
+static BSOCK *open_sd_bsock(UAContext *ua)
+{
+   STORE *store = ua->jcr->wstore;
+
+   if (!ua->jcr->store_bsock) {
+      ua->send_msg(_("Connecting to Storage daemon %s at %s:%d ...\n"),
+         store->name(), store->address, store->SDport);
+      if (!connect_to_storage_daemon(ua->jcr, 10, SDConnectTimeout, 1)) {
+         ua->error_msg(_("Failed to connect to Storage daemon.\n"));
+         return NULL;
+      }
+   }
+   return ua->jcr->store_bsock;
+}
+
+/* 
+ * 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)
+{
+   int dvd;
+   bool ok=false;
+   uint64_t VolBytes = 0;
+   
+   /* TODO: Return if not mr->Recyle ? */
+   if (!mr->Recycle) {
+      return;
+   }
+
+   /* 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;
+      set_storageid_in_mr(NULL, mr);
+      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));
+
+   /* 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 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];
+      }
+   }
+
+   /* Choose storage */
+   ua->jcr->wstore = store =  get_storage_resource(ua, false);
+   if (!store) {
+      goto bail_out;
+   }
+
+   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;
+   set_storageid_in_mr(store, &mr);
+   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++) {
+      mr.clear();
+      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);
+         }
+      } else {
+         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;
+}
+
 /*
  * IF volume status is Append, Full, Used, or Error, mark it Purged
  *   Purged volumes can then be recycled (if enabled).
@@ -500,11 +762,13 @@ bool mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
        strcmp(mr->VolStatus, "Used")   == 0 ||
        strcmp(mr->VolStatus, "Error")  == 0) {
       bstrncpy(mr->VolStatus, "Purged", sizeof(mr->VolStatus));
+      set_storageid_in_mr(NULL, mr);
       if (!db_update_media_record(jcr, ua->db, mr)) {
          return false;
       }
       pm_strcpy(jcr->VolumeName, mr->VolumeName);
       generate_job_event(jcr, "VolumePurged");
+      generate_plugin_event(jcr, bDirEventVolumePurged);
       /*
        * If the RecyclePool is defined, move the volume there
        */
@@ -530,9 +794,10 @@ bool mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
             ua->error_msg("%s", db_strerror(ua->db));
          }
       }
+
       /* Send message to Job report, if it is a *real* job */           
       if (jcr && jcr->JobId > 0) {
-         Jmsg1(jcr, M_INFO, 0, _("All records pruned from Volume \"%s\"; marking it \"Purged\"\n"),
+         Jmsg(jcr, M_INFO, 0, _("All records pruned from Volume \"%s\"; marking it \"Purged\"\n"),
             mr->VolumeName); 
       }
       return true;