]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_purge.c
b2c4a502151f05ea1f0937872653a6e95c068a31
[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  *     $Id$
12  */
13
14 /*
15    Copyright (C) 2002 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 #include "ua.h"
37
38 /* Forward referenced functions */
39 int purge_files_from_client(UAContext *ua, CLIENT *client);
40 int purge_jobs_from_client(UAContext *ua, CLIENT *client);
41 void purge_files_from_volume(UAContext *ua, POOL_DBR *pr, MEDIA_DBR *mr );
42 void purge_jobs_from_volume(UAContext *ua, POOL_DBR *pr, MEDIA_DBR *mr);
43 void purge_files_from_job(UAContext *ua, JOB_DBR *jr);
44 static int mark_media_purged(UAContext *ua, MEDIA_DBR *mr);
45
46
47 #define MAX_DEL_LIST_LEN 1000000
48
49
50 static char *select_jobsfiles_from_client =
51    "SELECT JobId FROM Job "
52    "WHERE ClientId=%d "
53    "AND PurgedFiles=0";
54
55 static char *select_jobs_from_client =
56    "SELECT JobId, PurgedFiles FROM Job "
57    "WHERE ClientId=%d";
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 = atoi(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)strtod(row[0], NULL);
137    del->PurgedFiles[del->num_ids++] = (char)atoi(row[0]);
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)strtod(row[0], NULL);
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, char *cmd)
166 {
167    CLIENT *client;
168    MEDIA_DBR mr;
169    POOL_DBR pr;
170    JOB_DBR  jr;
171    static char *keywords[] = {
172       N_("files"),
173       N_("jobs"),
174       NULL};
175
176    static char *files_keywords[] = {
177       N_("Job"),
178       N_("JobId"),
179       N_("Client"),
180       N_("Volume"),
181       NULL};
182
183    static char *jobs_keywords[] = {
184       N_("Client"),
185       N_("Volume"),
186       NULL};
187       
188    bsendmsg(ua, _(
189       "This command is DANGEROUS!!!\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. Normally you\n" 
193       "should use the PRUNE command instead.\n"));
194
195    if (!open_db(ua)) {
196       return 1;
197    }
198    switch (find_arg_keyword(ua, keywords)) {
199    /* Files */
200    case 0:
201       switch(find_arg_keyword(ua, files_keywords)) {
202       case 0:                         /* Job */
203       case 1:
204          if (get_job_dbr(ua, &jr)) {
205             purge_files_from_job(ua, &jr);
206          }
207          return 1;
208       case 2:                         /* client */
209          client = select_client_resource(ua);
210          purge_files_from_client(ua, client);
211          return 1;
212       case 3:
213          if (select_pool_and_media_dbr(ua, &pr, &mr)) {
214             purge_files_from_volume(ua, &pr, &mr);
215          }
216          return 1;
217       }
218    /* Jobs */
219    case 1:
220       switch(find_arg_keyword(ua, jobs_keywords)) {
221       case 0:                         /* client */
222          client = select_client_resource(ua);
223          purge_jobs_from_client(ua, client);
224          return 1;
225       case 1:
226          if (select_pool_and_media_dbr(ua, &pr, &mr)) {
227             purge_jobs_from_volume(ua, &pr, &mr);
228          }
229          return 1;
230       }
231    default:
232       break;
233    }
234    switch (do_keyword_prompt(ua, _("Choose item to purge"), keywords)) {
235    case 0:
236       client = select_client_resource(ua);
237       if (!client) {
238          return 1;
239       }
240       purge_files_from_client(ua, client);
241       break;
242    case 1:
243       client = select_client_resource(ua);
244       if (!client) {
245          return 1;
246       }
247       purge_jobs_from_client(ua, client);
248       break;
249    }
250    return 1;
251 }
252
253 /*
254  * Prune File records from the database. For any Job which
255  * is older than the retention period, we unconditionally delete
256  * all File records for that Job.  This is simple enough that no
257  * temporary tables are needed. We simply make an in memory list of
258  * the JobIds meeting the prune conditions, then delete all File records
259  * pointing to each of those JobIds.
260  */
261 int purge_files_from_client(UAContext *ua, CLIENT *client)
262 {
263    struct s_file_del_ctx del;
264    char *query = (char *)get_pool_memory(PM_MESSAGE);
265    int i;
266    CLIENT_DBR cr;
267
268    memset(&cr, 0, sizeof(cr));
269    memset(&del, 0, sizeof(del));
270
271    strcpy(cr.Name, client->hdr.name);
272    if (!db_create_client_record(ua->db, &cr)) {
273       return 0;
274    }
275
276    Mmsg(&query, select_jobsfiles_from_client, cr.ClientId);
277
278    Dmsg1(050, "select sql=%s\n", query);
279  
280    if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
281       bsendmsg(ua, "%s", db_strerror(ua->db));
282       Dmsg0(050, "Count failed\n");
283       goto bail_out;
284    }
285       
286    if (del.tot_ids == 0) {
287       bsendmsg(ua, _("No Files found for client %s to purge from %s catalog.\n"),
288          client->hdr.name, client->catalog->hdr.name);
289       goto bail_out;
290    }
291
292    if (del.tot_ids < MAX_DEL_LIST_LEN) {
293       del.max_ids = del.tot_ids + 1;
294    } else {
295       del.max_ids = MAX_DEL_LIST_LEN; 
296    }
297    del.tot_ids = 0;
298
299    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
300
301    db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
302
303    for (i=0; i < del.num_ids; i++) {
304       Dmsg1(050, "Delete JobId=%d\n", del.JobId[i]);
305       Mmsg(&query, "DELETE FROM File WHERE JobId=%d", del.JobId[i]);
306       db_sql_query(ua->db, query, NULL, (void *)NULL);
307       /* 
308        * Now mark Job as having files purged. This is necessary to
309        * avoid having too many Jobs to process in future prunings. If
310        * we don't do this, the number of JobId's in our in memory list
311        * will grow very large.
312        */
313       Mmsg(&query, "UPDATE Job Set PurgedFiles=1 WHERE JobId=%d", del.JobId[i]);
314       db_sql_query(ua->db, query, NULL, (void *)NULL);
315       Dmsg1(050, "Del sql=%s\n", query);
316    }
317    bsendmsg(ua, _("%d Files for client %s purged from %s catalog.\n"), del.num_ids,
318       client->hdr.name, client->catalog->hdr.name);
319    
320 bail_out:
321    if (del.JobId) {
322       free(del.JobId);
323    }
324    free_pool_memory(query);
325    return 1;
326 }
327
328
329
330 /*
331  * Purging Jobs is a bit more complicated than purging Files
332  * because we delete Job records only if there is a more current
333  * backup of the FileSet. Otherwise, we keep the Job record.
334  * In other words, we never delete the only Job record that
335  * contains a current backup of a FileSet. This prevents the
336  * Volume from being recycled and destroying a current backup.
337  */
338 int purge_jobs_from_client(UAContext *ua, CLIENT *client)
339 {
340    struct s_job_del_ctx del;
341    char *query = (char *)get_pool_memory(PM_MESSAGE);
342    int i;
343    CLIENT_DBR cr;
344
345    memset(&cr, 0, sizeof(cr));
346    memset(&del, 0, sizeof(del));
347
348    strcpy(cr.Name, client->hdr.name);
349    if (!db_create_client_record(ua->db, &cr)) {
350       return 0;
351    }
352
353    Mmsg(&query, select_jobs_from_client, cr.ClientId);
354
355    Dmsg1(050, "select sql=%s\n", query);
356  
357    if (!db_sql_query(ua->db, query, job_count_handler, (void *)&del)) {
358       bsendmsg(ua, "%s", db_strerror(ua->db));
359       Dmsg0(050, "Count failed\n");
360       goto bail_out;
361    }
362    if (del.tot_ids == 0) {
363       bsendmsg(ua, _("No Jobs found for client %s to purge from %s catalog.\n"),
364          client->hdr.name, client->catalog->hdr.name);
365       goto bail_out;
366    }
367
368    if (del.tot_ids < MAX_DEL_LIST_LEN) {
369       del.max_ids = del.tot_ids + 1;
370    } else {
371       del.max_ids = MAX_DEL_LIST_LEN; 
372    }
373
374    del.tot_ids = 0;
375
376    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
377    del.PurgedFiles = (char *)malloc(del.max_ids);
378
379    db_sql_query(ua->db, query, job_delete_handler, (void *)&del);
380
381    /* 
382     * OK, now we have the list of JobId's to be purged, first check
383     * if the Files have been purged, if not, purge (delete) them.
384     * Then delete the Job entry, and finally and JobMedia records.
385     */
386    for (i=0; i < del.num_ids; i++) {
387       Dmsg1(050, "Delete JobId=%d\n", del.JobId[i]);
388       if (!del.PurgedFiles[i]) {
389          Mmsg(&query, "DELETE FROM File WHERE JobId=%d", del.JobId[i]);
390          db_sql_query(ua->db, query, NULL, (void *)NULL);
391          Dmsg1(050, "Del sql=%s\n", query);
392       }
393
394       Mmsg(&query, "DELETE FROM Job WHERE JobId=%d", del.JobId[i]);
395       db_sql_query(ua->db, query, NULL, (void *)NULL);
396       Dmsg1(050, "Del sql=%s\n", query);
397
398       Mmsg(&query, "DELETE FROM JobMedia WHERE JobId=%d", del.JobId[i]);
399       db_sql_query(ua->db, query, NULL, (void *)NULL);
400       Dmsg1(050, "Del sql=%s\n", query);
401    }
402    bsendmsg(ua, _("%d Jobs for client %s purged from %s catalog.\n"), del.num_ids,
403       client->hdr.name, client->catalog->hdr.name);
404    
405 bail_out:
406    if (del.JobId) {
407       free(del.JobId);
408    }
409    if (del.PurgedFiles) {
410       free(del.PurgedFiles);
411    }
412    free_pool_memory(query);
413    return 1;
414 }
415
416 void purge_files_from_job(UAContext *ua, JOB_DBR *jr)
417 {
418    char *query = (char *)get_pool_memory(PM_MESSAGE);
419    
420    Mmsg(&query, "DELETE FROM File WHERE JobId=%d", jr->JobId);
421    db_sql_query(ua->db, query, NULL, (void *)NULL);
422
423    Mmsg(&query, "UPDATE Job Set PurgedFiles=1 WHERE JobId=%d", jr->JobId);
424    db_sql_query(ua->db, query, NULL, (void *)NULL);
425
426    free_pool_memory(query);
427 }
428
429 void purge_files_from_volume(UAContext *ua, POOL_DBR *pr, MEDIA_DBR *mr ) 
430 {} /* ***FIXME*** implement */
431
432 void purge_jobs_from_volume(UAContext *ua, POOL_DBR *pr, MEDIA_DBR *mr) 
433 {
434    char *query = (char *)get_pool_memory(PM_MESSAGE);
435    struct s_count_ctx cnt;
436    struct s_file_del_ctx del;
437    int i;
438    JOB_DBR jr;
439
440    memset(&jr, 0, sizeof(jr));
441    memset(&del, 0, sizeof(del));
442    cnt.count = 0;
443    Mmsg(&query, "SELECT count(*) FROM JobMedia WHERE MediaId=%d", mr->MediaId);
444    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
445       bsendmsg(ua, "%s", db_strerror(ua->db));
446       Dmsg0(050, "Count failed\n");
447       goto bail_out;
448    }
449       
450    if (cnt.count == 0) {
451       bsendmsg(ua, "There are no Jobs associated with Volume %s. It is purged.\n",
452          mr->VolumeName);
453       if (!mark_media_purged(ua, mr)) {
454          goto bail_out;
455       }
456       goto bail_out;
457    }
458
459    if (cnt.count < MAX_DEL_LIST_LEN) {
460       del.max_ids = cnt.count + 1;
461    } else {
462       del.max_ids = MAX_DEL_LIST_LEN; 
463    }
464
465    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
466
467    Mmsg(&query, "SELECT JobId FROM JobMedia WHERE MediaId=%d", mr->MediaId);
468    if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
469       bsendmsg(ua, "%s", db_strerror(ua->db));
470       Dmsg0(050, "Count failed\n");
471       goto bail_out;
472    }
473
474    for (i=0; i < del.num_ids; i++) {
475       Dmsg1(050, "Delete JobId=%d\n", del.JobId[i]);
476       Mmsg(&query, "DELETE FROM File WHERE JobId=%d", del.JobId[i]);
477       db_sql_query(ua->db, query, NULL, (void *)NULL);
478       Mmsg(&query, "DELETE FROM Job WHERE JobId=%d", del.JobId[i]);
479       db_sql_query(ua->db, query, NULL, (void *)NULL);
480       Mmsg(&query, "DELETE FROM JobMedia WHERE JobId=%d", del.JobId[i]);
481       db_sql_query(ua->db, query, NULL, (void *)NULL);
482       Dmsg1(050, "Del sql=%s\n", query);
483       del.num_del++;
484    }
485    if (del.JobId) {
486       free(del.JobId);
487    }
488    bsendmsg(ua, _("%d Files for Volume %s purged from catalog.\n"), del.num_del,
489       mr->VolumeName);
490
491    /* If purged, mark it so */
492    if (del.num_ids == del.num_del) {
493       mark_media_purged(ua, mr);
494    }
495
496 bail_out:   
497    free_pool_memory(query);
498 }
499
500 static int mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
501 {
502    if (strcmp(mr->VolStatus, "Append") == 0 || 
503        strcmp(mr->VolStatus, "Full")   == 0) {
504       strcpy(mr->VolStatus, "Purged");
505       if (!db_update_media_record(ua->db, mr)) {
506          bsendmsg(ua, "%s", db_strerror(ua->db));
507          return 0;
508       }
509    }
510    return 1;
511 }