]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/dird/ua_purge.c
Fix #1467 about ActionOnPurge with Devices having space
[bacula/bacula] / bacula / src / dird / ua_purge.c
index c08fe06c556365e7d0c837e07696596c832c7467..dc101f15002f953d8e51763a65b6d45415f55e55 100644 (file)
@@ -1,14 +1,14 @@
 /*
    Bacula® - The Network Backup Solution
 
-   Copyright (C) 2002-2007 Free Software Foundation Europe e.V.
+   Copyright (C) 2002-2008 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.
+   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
@@ -20,7 +20,7 @@
    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.
@@ -86,12 +86,12 @@ int purgecmd(UAContext *ua, const char *cmd)
       NT_("Volume"),
       NULL};
 
-   bsendmsg(ua, _(
-      "\nThis command is can be DANGEROUS!!!\n\n"
+   ua->warning_msg(_(
+      "\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,7 +132,7 @@ 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;
       }
@@ -140,10 +140,10 @@ int purgecmd(UAContext *ua, const char *cmd)
    case 2:
       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 */
-         bsendmsg(ua, "\n");
+         ua->send_msg("\n");
       }
       return 1;
    default:
@@ -164,7 +164,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;
    }
@@ -196,7 +196,7 @@ static int purge_files_from_client(UAContext *ua, CLIENT *client)
    del.max_ids = 1000;
    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
 
-   bsendmsg(ua, _("Begin purging files for Client \"%s\"\n"), cr.Name);
+   ua->info_msg(_("Begin purging files for Client \"%s\"\n"), cr.Name);
 
    Mmsg(query, select_jobsfiles_from_client, edit_int64(cr.ClientId, ed1));
    Dmsg1(050, "select sql=%s\n", query.c_str());
@@ -205,10 +205,10 @@ static int purge_files_from_client(UAContext *ua, CLIENT *client)
    purge_files_from_job_list(ua, del);
 
    if (del.num_ids == 0) {
-      bsendmsg(ua, _("No Files found for client %s to purge from %s catalog.\n"),
+      ua->warning_msg(_("No Files found for client %s to purge from %s catalog.\n"),
          client->name(), client->catalog->name());
    } else {
-      bsendmsg(ua, _("Files for %d Jobs for client \"%s\" purged from %s catalog.\n"), del.num_ids,
+      ua->info_msg(_("Files for %d Jobs for client \"%s\" purged from %s catalog.\n"), del.num_ids,
          client->name(), client->catalog->name());
    }
 
@@ -246,7 +246,7 @@ static int purge_jobs_from_client(UAContext *ua, CLIENT *client)
    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
    del.PurgedFiles = (char *)malloc(del.max_ids);
    
-   bsendmsg(ua, _("Begin purging jobs from Client \"%s\"\n"), cr.Name);
+   ua->info_msg(_("Begin purging jobs from Client \"%s\"\n"), cr.Name);
 
    Mmsg(query, select_jobs_from_client, edit_int64(cr.ClientId, ed1));
    Dmsg1(150, "select sql=%s\n", query.c_str());
@@ -255,10 +255,10 @@ static int purge_jobs_from_client(UAContext *ua, CLIENT *client)
    purge_job_list_from_catalog(ua, del);
 
    if (del.num_ids == 0) {
-      bsendmsg(ua, _("No Files found for client %s to purge from %s catalog.\n"),
+      ua->warning_msg(_("No Files found for client %s to purge from %s catalog.\n"),
          client->name(), client->catalog->name());
    } else {
-      bsendmsg(ua, _("%d Jobs for client %s purged from %s catalog.\n"), del.num_ids,
+      ua->info_msg(_("%d Jobs for client %s purged from %s catalog.\n"), del.num_ids,
          client->name(), client->catalog->name());
    }
 
@@ -283,6 +283,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 +361,57 @@ 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, "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);
+   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 +430,15 @@ 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());
 
+   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,7 +446,7 @@ 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;
@@ -404,11 +461,10 @@ bool purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr)
           strcmp(mr->VolStatus, "Used")   == 0 ||
           strcmp(mr->VolStatus, "Error")  == 0;
    if (!stat) {
-      bsendmsg(ua, "\n");
-      bsendmsg(ua, _("Volume \"%s\" has VolStatus \"%s\" and cannot be purged.\n"
+      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);
-      goto bail_out;
+      return 0;
    }
 
    memset(&jr, 0, sizeof(jr));
@@ -430,7 +486,7 @@ bool purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr)
       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)) {
-         bsendmsg(ua, "%s", db_strerror(ua->db));
+         ua->error_msg("%s", db_strerror(ua->db));
          Dmsg0(050, "Count failed\n");
          goto bail_out;
       }
@@ -438,10 +494,10 @@ bool purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr)
 
    purge_job_list_from_catalog(ua, del);
 
-   bsendmsg(ua, _("%d File%s on Volume \"%s\" purged from catalog.\n"), del.num_del,
+   ua->info_msg(_("%d File%s on Volume \"%s\" purged from catalog.\n"), del.num_del,
       del.num_del==1?"":"s", mr->VolumeName);
 
-   purged = is_volume_purged(ua, mr);
+   purged = is_volume_purged(ua, mr, force); 
 
 bail_out:
    if (del.JobId) {
@@ -456,45 +512,72 @@ 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", 
         edit_int64(mr->MediaId, ed1));
    if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
-      bsendmsg(ua, "%s", db_strerror(ua->db));
+      ua->error_msg("%s", db_strerror(ua->db));
       Dmsg0(050, "Count failed\n");
       goto bail_out;
    }
 
    if (cnt.count == 0) {
-      bsendmsg(ua, _("There are no more Jobs associated with Volume \"%s\". Marking it purged.\n"),
+      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))) {
-         bsendmsg(ua, "%s", db_strerror(ua->db));
+         ua->error_msg("%s", db_strerror(ua->db));
       }
    }
 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;
+}
+
 /*
  * IF volume status is Append, Full, Used, or Error, mark it Purged
  *   Purged volumes can then be recycled (if enabled).
  */
 bool mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
 {
+   char dev_name[MAX_NAME_LENGTH];
    JCR *jcr = ua->jcr;
    if (strcmp(mr->VolStatus, "Append") == 0 ||
        strcmp(mr->VolStatus, "Full")   == 0 ||
@@ -504,8 +587,37 @@ bool mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
       if (!db_update_media_record(jcr, ua->db, mr)) {
          return false;
       }
+
+      if (mr->ActionOnPurge > 0) {
+         /* Send the command to truncate the volume after purge. If this feature
+          * is disabled for the specific device, this will be a no-op.
+          */
+         BSOCK *sd;
+         if ((sd=open_sd_bsock(ua)) != NULL) {
+            bstrncpy(dev_name, ua->jcr->wstore->dev_name(), sizeof(dev_name));
+            bash_spaces(dev_name);
+            bash_spaces(mr->VolumeName);
+            sd->fsend("action_on_purge %s vol=%s action=%d",
+                      ua->jcr->wstore->dev_name(),
+                     mr->VolumeName,
+                     mr->ActionOnPurge);
+            unbash_spaces(mr->VolumeName);
+            while (sd->recv() >= 0) {
+               ua->send_msg("%s", sd->msg);
+            }
+
+            sd->signal(BNET_TERMINATE);
+            sd->close();
+            ua->jcr->store_bsock = NULL;
+         } else {
+            ua->error_msg(_("Could not connect to storage daemon"));
+           return false;
+        }
+      }
+
       pm_strcpy(jcr->VolumeName, mr->VolumeName);
       generate_job_event(jcr, "VolumePurged");
+      generate_plugin_event(jcr, bEventVolumePurged);
       /*
        * If the RecyclePool is defined, move the volume there
        */
@@ -520,7 +632,7 @@ bool mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
          {
             /* check if destination pool size is ok */
             if (newpr.MaxVols > 0 && newpr.NumVols >= newpr.MaxVols) {
-               bsendmsg(ua, _("Unable move recycled Volume in full " 
+               ua->error_msg(_("Unable move recycled Volume in full " 
                               "Pool \"%s\" MaxVols=%d\n"),
                         newpr.Name, newpr.MaxVols);
 
@@ -528,17 +640,17 @@ bool mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
                update_vol_pool(ua, newpr.Name, mr, &oldpr);
             }
          } else {
-            bsendmsg(ua, "%s", db_strerror(ua->db));
+            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;
    } else {
-      bsendmsg(ua, _("Cannot purge Volume with VolStatus=%s\n"), mr->VolStatus);
+      ua->error_msg(_("Cannot purge Volume with VolStatus=%s\n"), mr->VolStatus);
    }
    return strcmp(mr->VolStatus, "Purged") == 0;
 }