]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_purge.c
Update copyright
[bacula/bacula] / bacula / src / dird / ua_purge.c
1 /*
2  *
3  *   Bacula Director -- User Agent Database Purge Command
4  *
5  *      Purges Files from specific JobIds
6  * or
7  *      Purges Jobs from Volumes
8  *
9  *     Kern Sibbald, February MMII
10  *
11  *   Version $Id$
12  */
13 /*
14    Bacula® - The Network Backup Solution
15
16    Copyright (C) 2002-2006 Free Software Foundation Europe e.V.
17
18    The main author of Bacula is Kern Sibbald, with contributions from
19    many others, a complete list can be found in the file AUTHORS.
20    This program is Free Software; you can redistribute it and/or
21    modify it under the terms of version two of the GNU General Public
22    License as published by the Free Software Foundation plus additions
23    that are listed in the file LICENSE.
24
25    This program is distributed in the hope that it will be useful, but
26    WITHOUT ANY WARRANTY; without even the implied warranty of
27    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
28    General Public License for more details.
29
30    You should have received a copy of the GNU General Public License
31    along with this program; if not, write to the Free Software
32    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
33    02110-1301, USA.
34
35    Bacula® is a registered trademark of John Walker.
36    The licensor of Bacula is the Free Software Foundation Europe
37    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
38    Switzerland, email:ftf@fsfeurope.org.
39 */
40
41 #include "bacula.h"
42 #include "dird.h"
43
44 /* Forward referenced functions */
45 static int purge_files_from_client(UAContext *ua, CLIENT *client);
46 static int purge_jobs_from_client(UAContext *ua, CLIENT *client);
47
48 #define MAX_DEL_LIST_LEN 1000000
49
50 static const char *select_jobsfiles_from_client =
51    "SELECT JobId FROM Job "
52    "WHERE ClientId=%s "
53    "AND PurgedFiles=0";
54
55 static const char *select_jobs_from_client =
56    "SELECT JobId, PurgedFiles FROM Job "
57    "WHERE ClientId=%s";
58
59
60 /* In memory list of JobIds */
61 struct s_file_del_ctx {
62    JobId_t *JobId;
63    int num_ids;                       /* ids stored */
64    int max_ids;                       /* size of array */
65    int num_del;                       /* number deleted */
66    int tot_ids;                       /* total to process */
67 };
68
69 struct s_job_del_ctx {
70    JobId_t *JobId;                    /* array of JobIds */
71    char *PurgedFiles;                 /* Array of PurgedFile flags */
72    int num_ids;                       /* ids stored */
73    int max_ids;                       /* size of array */
74    int num_del;                       /* number deleted */
75    int tot_ids;                       /* total to process */
76 };
77
78 struct s_count_ctx {
79    int count;
80 };
81
82 /*
83  * Called here to count entries to be deleted
84  */
85 static int count_handler(void *ctx, int num_fields, char **row)
86 {
87    struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
88
89    if (row[0]) {
90       cnt->count = str_to_int64(row[0]);
91    } else {
92       cnt->count = 0;
93    }
94    return 0;
95 }
96
97 /*
98  * Called here to count entries to be deleted
99  */
100 static int file_count_handler(void *ctx, int num_fields, char **row)
101 {
102    struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
103    del->tot_ids++;
104    return 0;
105 }
106
107
108 static int job_count_handler(void *ctx, int num_fields, char **row)
109 {
110    struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
111    del->tot_ids++;
112    return 0;
113 }
114
115
116 /*
117  * Called here to make in memory list of JobIds to be
118  *  deleted and the associated PurgedFiles flag.
119  *  The in memory list will then be transversed
120  *  to issue the SQL DELETE commands.  Note, the list
121  *  is allowed to get to MAX_DEL_LIST_LEN to limit the
122  *  maximum malloc'ed memory.
123  */
124 static int job_delete_handler(void *ctx, int num_fields, char **row)
125 {
126    struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
127
128    if (del->num_ids == MAX_DEL_LIST_LEN) {
129       return 1;
130    }
131    if (del->num_ids == del->max_ids) {
132       del->max_ids = (del->max_ids * 3) / 2;
133       del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
134       del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
135    }
136    del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
137    del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[1]);
138    return 0;
139 }
140
141 static int file_delete_handler(void *ctx, int num_fields, char **row)
142 {
143    struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
144
145    if (del->num_ids == MAX_DEL_LIST_LEN) {
146       return 1;
147    }
148    if (del->num_ids == del->max_ids) {
149       del->max_ids = (del->max_ids * 3) / 2;
150       del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
151          del->max_ids);
152    }
153    del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
154    return 0;
155 }
156
157 /*
158  *   Purge records from database
159  *
160  *     Purge Files (from) [Job|JobId|Client|Volume]
161  *     Purge Jobs  (from) [Client|Volume]
162  *
163  *  N.B. Not all above is implemented yet.
164  */
165 int purgecmd(UAContext *ua, const char *cmd)
166 {
167    int i;
168    CLIENT *client;
169    MEDIA_DBR mr;
170    JOB_DBR  jr;
171    static const char *keywords[] = {
172       NT_("files"),
173       NT_("jobs"),
174       NT_("volume"),
175       NULL};
176
177    static const char *files_keywords[] = {
178       NT_("Job"),
179       NT_("JobId"),
180       NT_("Client"),
181       NT_("Volume"),
182       NULL};
183
184    static const char *jobs_keywords[] = {
185       NT_("Client"),
186       NT_("Volume"),
187       NULL};
188
189    bsendmsg(ua, _(
190       "\nThis command is can be DANGEROUS!!!\n\n"
191       "It purges (deletes) all Files from a Job,\n"
192       "JobId, Client or Volume; or it purges (deletes)\n"
193       "all Jobs from a Client or Volume without regard\n"
194       "for retention periods. Normally you should use the\n"
195       "PRUNE command, which respects retention periods.\n"));
196
197    if (!open_db(ua)) {
198       return 1;
199    }
200    switch (find_arg_keyword(ua, keywords)) {
201    /* Files */
202    case 0:
203       switch(find_arg_keyword(ua, files_keywords)) {
204       case 0:                         /* Job */
205       case 1:                         /* JobId */
206          if (get_job_dbr(ua, &jr)) {
207             purge_files_from_job(ua, jr.JobId);
208          }
209          return 1;
210       case 2:                         /* client */
211          client = get_client_resource(ua);
212          if (client) {
213             purge_files_from_client(ua, client);
214          }
215          return 1;
216       case 3:                         /* Volume */
217          if (select_media_dbr(ua, &mr)) {
218             purge_files_from_volume(ua, &mr);
219          }
220          return 1;
221       }
222    /* Jobs */
223    case 1:
224       switch(find_arg_keyword(ua, jobs_keywords)) {
225       case 0:                         /* client */
226          client = get_client_resource(ua);
227          if (client) {
228             purge_jobs_from_client(ua, client);
229          }
230          return 1;
231       case 1:                         /* Volume */
232          if (select_media_dbr(ua, &mr)) {
233             purge_jobs_from_volume(ua, &mr);
234          }
235          return 1;
236       }
237    /* Volume */
238    case 2:
239       while ((i=find_arg(ua, NT_("volume"))) >= 0) {
240          if (select_media_dbr(ua, &mr)) {
241             purge_jobs_from_volume(ua, &mr);
242          }
243          *ua->argk[i] = 0;            /* zap keyword already seen */
244          bsendmsg(ua, "\n");
245       }
246       return 1;
247    default:
248       break;
249    }
250    switch (do_keyword_prompt(ua, _("Choose item to purge"), keywords)) {
251    case 0:                            /* files */
252       client = get_client_resource(ua);
253       if (client) {
254          purge_files_from_client(ua, client);
255       }
256       break;
257    case 1:                            /* jobs */
258       client = get_client_resource(ua);
259       if (client) {
260          purge_jobs_from_client(ua, client);
261       }
262       break;
263    case 2:                            /* Volume */
264       if (select_media_dbr(ua, &mr)) {
265          purge_jobs_from_volume(ua, &mr);
266       }
267       break;
268    }
269    return 1;
270 }
271
272 /*
273  * Purge File records from the database. For any Job which
274  * is older than the retention period, we unconditionally delete
275  * all File records for that Job.  This is simple enough that no
276  * temporary tables are needed. We simply make an in memory list of
277  * the JobIds meeting the prune conditions, then delete all File records
278  * pointing to each of those JobIds.
279  */
280 static int purge_files_from_client(UAContext *ua, CLIENT *client)
281 {
282    struct s_file_del_ctx del;
283    POOLMEM *query = get_pool_memory(PM_MESSAGE);
284    int i;
285    CLIENT_DBR cr;
286    char ed1[50];
287
288    memset(&cr, 0, sizeof(cr));
289    memset(&del, 0, sizeof(del));
290
291    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
292    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
293       return 0;
294    }
295    bsendmsg(ua, _("Begin purging files for Client \"%s\"\n"), cr.Name);
296    Mmsg(query, select_jobsfiles_from_client, edit_int64(cr.ClientId, ed1));
297
298    Dmsg1(050, "select sql=%s\n", query);
299
300    if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
301       bsendmsg(ua, "%s", db_strerror(ua->db));
302       Dmsg0(050, "Count failed\n");
303       goto bail_out;
304    }
305
306    if (del.tot_ids == 0) {
307       bsendmsg(ua, _("No Files found for client %s to purge from %s catalog.\n"),
308          client->hdr.name, client->catalog->hdr.name);
309       goto bail_out;
310    }
311
312    if (del.tot_ids < MAX_DEL_LIST_LEN) {
313       del.max_ids = del.tot_ids + 1;
314    } else {
315       del.max_ids = MAX_DEL_LIST_LEN;
316    }
317    del.tot_ids = 0;
318
319    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
320
321    db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
322
323    for (i=0; i < del.num_ids; i++) {
324       purge_files_from_job(ua, del.JobId[i]);
325    }
326    bsendmsg(ua, _("%d Files for client \"%s\" purged from %s catalog.\n"), del.num_ids,
327       client->hdr.name, client->catalog->hdr.name);
328
329 bail_out:
330    if (del.JobId) {
331       free(del.JobId);
332    }
333    free_pool_memory(query);
334    return 1;
335 }
336
337
338
339 /*
340  * Purge Job records from the database. For any Job which
341  * is older than the retention period, we unconditionally delete
342  * it and all File records for that Job.  This is simple enough that no
343  * temporary tables are needed. We simply make an in memory list of
344  * the JobIds then delete the Job, Files, and JobMedia records in that list.
345  */
346 static int purge_jobs_from_client(UAContext *ua, CLIENT *client)
347 {
348    struct s_job_del_ctx del;
349    POOLMEM *query = get_pool_memory(PM_MESSAGE);
350    int i;
351    CLIENT_DBR cr;
352    char ed1[50];
353
354    memset(&cr, 0, sizeof(cr));
355    memset(&del, 0, sizeof(del));
356
357    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
358    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
359       return 0;
360    }
361
362    bsendmsg(ua, _("Begin purging jobs from Client \"%s\"\n"), cr.Name);
363    Mmsg(query, select_jobs_from_client, edit_int64(cr.ClientId, ed1));
364
365    Dmsg1(050, "select sql=%s\n", query);
366
367    if (!db_sql_query(ua->db, query, job_count_handler, (void *)&del)) {
368       bsendmsg(ua, "%s", db_strerror(ua->db));
369       Dmsg0(050, "Count failed\n");
370       goto bail_out;
371    }
372    if (del.tot_ids == 0) {
373       bsendmsg(ua, _("No Jobs found for client %s to purge from %s catalog.\n"),
374          client->hdr.name, client->catalog->hdr.name);
375       goto bail_out;
376    }
377
378    if (del.tot_ids < MAX_DEL_LIST_LEN) {
379       del.max_ids = del.tot_ids + 1;
380    } else {
381       del.max_ids = MAX_DEL_LIST_LEN;
382    }
383
384    del.tot_ids = 0;
385
386    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
387    del.PurgedFiles = (char *)malloc(del.max_ids);
388
389    db_sql_query(ua->db, query, job_delete_handler, (void *)&del);
390
391    /*
392     * OK, now we have the list of JobId's to be purged, first check
393     * if the Files have been purged, if not, purge (delete) them.
394     * Then delete the Job entry, and finally and JobMedia records.
395     */
396    for (i=0; i < del.num_ids; i++) {
397       Dmsg1(050, "Delete Files JobId=%s\n", ed1); 
398       if (!del.PurgedFiles[i]) {
399          purge_files_from_job(ua, del.JobId[i]);
400       }
401       purge_job_from_catalog(ua, del.JobId[i]);
402    }
403    bsendmsg(ua, _("%d Jobs for client %s purged from %s catalog.\n"), del.num_ids,
404       client->hdr.name, client->catalog->hdr.name);
405
406 bail_out:
407    if (del.JobId) {
408       free(del.JobId);
409    }
410    if (del.PurgedFiles) {
411       free(del.PurgedFiles);
412    }
413    free_pool_memory(query);
414    return 1;
415 }
416
417 void purge_job_from_catalog(UAContext *ua, JobId_t JobId)
418 {
419    POOL_MEM query(PM_MESSAGE);
420    char ed1[50];
421
422    edit_int64(JobId, ed1);
423    Mmsg(query, "DELETE FROM Job WHERE JobId=%s", ed1);
424    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
425    Dmsg1(050, "Delete Job sql=%s\n", query.c_str());
426
427    Mmsg(query, "DELETE FROM JobMedia WHERE JobId=%s", ed1);
428    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
429    Dmsg1(050, "Delete JobMedia sql=%s\n", query.c_str());
430
431    Mmsg(query, "DELETE FROM Log WHERE JobId=%s", ed1);
432    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
433    Dmsg1(050, "Delete Log sql=%s\n", query.c_str());
434
435 }
436
437 void purge_files_from_job(UAContext *ua, JobId_t JobId)
438 {
439    POOL_MEM query(PM_MESSAGE);
440    char ed1[50];
441
442    edit_int64(JobId, ed1);
443    Mmsg(query, del_File, ed1);
444    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
445
446    /*
447     * Now mark Job as having files purged. This is necessary to
448     * avoid having too many Jobs to process in future prunings. If
449     * we don't do this, the number of JobId's in our in memory list
450     * could grow very large.
451     */
452    Mmsg(query, upd_Purged, ed1);
453 }
454
455 void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr )
456 {} /* ***FIXME*** implement */
457
458 /*
459  * Returns: 1 if Volume purged
460  *          0 if Volume not purged
461  */
462 int purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr)
463 {
464    POOLMEM *query = get_pool_memory(PM_MESSAGE);
465    struct s_count_ctx cnt;
466    struct s_file_del_ctx del;
467    int i, stat = 0;
468    JOB_DBR jr;
469    char ed1[50];
470
471    stat = strcmp(mr->VolStatus, "Append") == 0 ||
472           strcmp(mr->VolStatus, "Full")   == 0 ||
473           strcmp(mr->VolStatus, "Used")   == 0 ||
474           strcmp(mr->VolStatus, "Error")  == 0;
475    if (!stat) {
476       bsendmsg(ua, "\n");
477       bsendmsg(ua, _("Volume \"%s\" has VolStatus \"%s\" and cannot be purged.\n"
478                      "The VolStatus must be: Append, Full, Used, or Error to be purged.\n"),
479                      mr->VolumeName, mr->VolStatus);
480       goto bail_out;
481    }
482
483    memset(&jr, 0, sizeof(jr));
484    memset(&del, 0, sizeof(del));
485    cnt.count = 0;
486    Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%s", 
487         edit_int64(mr->MediaId, ed1));
488    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
489       bsendmsg(ua, "%s", db_strerror(ua->db));
490       Dmsg0(050, "Count failed\n");
491       goto bail_out;
492    }
493
494    if (cnt.count == 0) {
495       bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
496          mr->VolumeName);
497       if (!mark_media_purged(ua, mr)) {
498          bsendmsg(ua, "%s", db_strerror(ua->db));
499          goto bail_out;
500       }
501       goto bail_out;
502    }
503
504    if (cnt.count < MAX_DEL_LIST_LEN) {
505       del.max_ids = cnt.count + 1;
506    } else {
507       del.max_ids = MAX_DEL_LIST_LEN;
508    }
509
510    /*
511     * Check if he wants to purge a single jobid
512     */
513    i = find_arg_with_value(ua, "jobid");
514    if (i >= 0) {
515       del.JobId = (JobId_t *)malloc(sizeof(JobId_t));
516       del.num_ids = 1;
517       del.JobId[0] = str_to_int64(ua->argv[i]);
518    } else {
519       /*
520        * Purge ALL JobIds
521        */
522       del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
523
524       Mmsg(query, "SELECT JobId FROM JobMedia WHERE MediaId=%s", 
525            edit_int64(mr->MediaId, ed1));
526       if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
527          bsendmsg(ua, "%s", db_strerror(ua->db));
528          Dmsg0(050, "Count failed\n");
529          goto bail_out;
530       }
531    }
532
533    for (i=0; i < del.num_ids; i++) {
534       purge_files_from_job(ua, del.JobId[i]);
535       purge_job_from_catalog(ua, del.JobId[i]);
536       del.num_del++;
537    }
538    if (del.JobId) {
539       free(del.JobId);
540    }
541    bsendmsg(ua, _("%d File%s on Volume \"%s\" purged from catalog.\n"), del.num_del,
542       del.num_del==1?"":"s", mr->VolumeName);
543
544    /* If purged, mark it so */
545    cnt.count = 0;
546    Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%s", 
547         edit_int64(mr->MediaId, ed1));
548    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
549       bsendmsg(ua, "%s", db_strerror(ua->db));
550       Dmsg0(050, "Count failed\n");
551       goto bail_out;
552    }
553
554    if (cnt.count == 0) {
555       bsendmsg(ua, _("There are no more Jobs associated with Volume \"%s\". Marking it purged.\n"),
556          mr->VolumeName);
557       if (!(stat = mark_media_purged(ua, mr))) {
558          bsendmsg(ua, "%s", db_strerror(ua->db));
559          goto bail_out;
560       }
561    }
562
563 bail_out:
564    free_pool_memory(query);
565    return stat;
566 }
567
568 /*
569  * IF volume status is Append, Full, Used, or Error, mark it Purged
570  *   Purged volumes can then be recycled (if enabled).
571  */
572 bool mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
573 {
574    JCR *jcr = ua->jcr;
575    if (strcmp(mr->VolStatus, "Append") == 0 ||
576        strcmp(mr->VolStatus, "Full")   == 0 ||
577        strcmp(mr->VolStatus, "Used")   == 0 ||
578        strcmp(mr->VolStatus, "Error")  == 0) {
579       bstrncpy(mr->VolStatus, "Purged", sizeof(mr->VolStatus));
580       if (!db_update_media_record(jcr, ua->db, mr)) {
581          return false;
582       }
583       pm_strcpy(jcr->VolumeName, mr->VolumeName);
584       generate_job_event(jcr, "VolumePurged");
585       /* Send message to Job report, if it is a *real* job */           
586       if (jcr && jcr->JobId > 0) {
587          Jmsg1(jcr, M_INFO, 0, _("All records pruned from Volume \"%s\"; marking it \"Purged\"\n"),
588             mr->VolumeName); 
589       }
590       return true;
591    } else {
592       bsendmsg(ua, _("Cannot purge Volume with VolStatus=%s\n"), mr->VolStatus);
593    }
594    return strcmp(mr->VolStatus, "Purged") == 0;
595 }