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