]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_prune.c
d72fe1d370854552dd1eed55afe3880e703be570
[bacula/bacula] / bacula / src / dird / ua_prune.c
1 /*
2  *
3  *   Bacula Director -- User Agent Database prune Command
4  *      Applies retention periods
5  *
6  *     Kern Sibbald, February MMII
7  *
8  *   Version $Id$
9  */
10
11 /*
12    Copyright (C) 2002-2004 Kern Sibbald and John Walker
13
14    This program is free software; you can redistribute it and/or
15    modify it under the terms of the GNU General Public License as
16    published by the Free Software Foundation; either version 2 of
17    the License, or (at your option) any later version.
18
19    This program is distributed in the hope that it will be useful,
20    but WITHOUT ANY WARRANTY; without even the implied warranty of
21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22    General Public License for more details.
23
24    You should have received a copy of the GNU General Public
25    License along with this program; if not, write to the Free
26    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27    MA 02111-1307, USA.
28
29  */
30
31 #include "bacula.h"
32 #include "dird.h"
33
34 /* Imported functions */
35 int mark_media_purged(UAContext *ua, MEDIA_DBR *mr);
36
37 /* Forward referenced functions */
38
39
40 #define MAX_DEL_LIST_LEN 1000000
41
42 /* Imported variables */
43 extern char *select_job;
44 extern char *drop_deltabs[];
45 extern char *create_deltabs[];
46 extern char *insert_delcand;
47 extern char *select_backup_del;
48 extern char *select_verify_del;
49 extern char *select_restore_del;
50 extern char *select_admin_del;
51 extern char *cnt_File;
52 extern char *del_File;
53 extern char *upd_Purged;
54 extern char *cnt_DelCand;
55 extern char *del_Job;
56 extern char *del_JobMedia;
57 extern char *cnt_JobMedia;
58 extern char *sel_JobMedia;
59
60
61 /* In memory list of JobIds */
62 struct s_file_del_ctx {
63    JobId_t *JobId;
64    int num_ids;                       /* ids stored */
65    int max_ids;                       /* size of array */
66    int num_del;                       /* number deleted */
67    int tot_ids;                       /* total to process */
68 };
69
70 struct s_job_del_ctx {
71    JobId_t *JobId;                    /* array of JobIds */
72    char *PurgedFiles;                 /* Array of PurgedFile flags */
73    int num_ids;                       /* ids stored */
74    int max_ids;                       /* size of array */
75    int num_del;                       /* number deleted */
76    int tot_ids;                       /* total to process */
77 };
78
79 struct s_count_ctx {
80    int count;
81 };
82
83
84 /*
85  * Called here to count entries to be deleted 
86  */
87 static int count_handler(void *ctx, int num_fields, char **row)
88 {
89    struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
90
91    if (row[0]) {
92       cnt->count = atoi(row[0]);
93    } else {
94       cnt->count = 0;
95    }
96    return 0;
97 }
98
99
100 /*
101  * Called here to count the number of Jobs to be pruned
102  */
103 static int file_count_handler(void *ctx, int num_fields, char **row)
104 {
105    struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
106    del->tot_ids++;
107    return 0;
108 }
109
110
111 /*
112  * Called here to make in memory list of JobIds to be
113  *  deleted and the associated PurgedFiles flag.
114  *  The in memory list will then be transversed
115  *  to issue the SQL DELETE commands.  Note, the list
116  *  is allowed to get to MAX_DEL_LIST_LEN to limit the
117  *  maximum malloc'ed memory.
118  */
119 static int job_delete_handler(void *ctx, int num_fields, char **row)
120 {
121    struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
122
123    if (del->num_ids == MAX_DEL_LIST_LEN) {  
124       return 1;
125    }
126    if (del->num_ids == del->max_ids) {
127       del->max_ids = (del->max_ids * 3) / 2;
128       del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
129       del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
130    }
131    del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
132    del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[0]);
133    return 0;
134 }
135
136 static int file_delete_handler(void *ctx, int num_fields, char **row)
137 {
138    struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
139
140    if (del->num_ids == MAX_DEL_LIST_LEN) {  
141       return 1;
142    }
143    if (del->num_ids == del->max_ids) {
144       del->max_ids = (del->max_ids * 3) / 2;
145       del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
146          del->max_ids);
147    }
148    del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
149    return 0;
150 }
151
152 /*
153  *   Prune records from database
154  *
155  *    prune files (from) client=xxx
156  *    prune jobs (from) client=xxx
157  *    prune volume=xxx  
158  */
159 int prunecmd(UAContext *ua, const char *cmd)
160 {
161    CLIENT *client;
162    POOL_DBR pr;
163    MEDIA_DBR mr;
164    int kw;
165
166    static const char *keywords[] = {
167       N_("Files"),
168       N_("Jobs"),
169       N_("Volume"),
170       NULL};
171
172    if (!open_db(ua)) {
173       return 01;
174    }
175
176    /* First search args */
177    kw = find_arg_keyword(ua, keywords);  
178    if (kw < 0 || kw > 2) {
179       /* no args, so ask user */
180       kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);  
181    }       
182     
183    switch (kw) {
184    case 0:  /* prune files */
185       client = get_client_resource(ua);
186       if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
187          return 0;
188       }
189       prune_files(ua, client);
190       return 1;
191    case 1:  /* prune jobs */
192       client = get_client_resource(ua);
193       if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
194          return 0;
195       }
196       /* ****FIXME**** allow user to select JobType */
197       prune_jobs(ua, client, JT_BACKUP);
198       return 1;
199    case 2:  /* prune volume */
200       if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
201          return 0;
202       }
203       if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
204          return 0;
205       }
206       prune_volume(ua, &mr);
207       return 1;
208    default:
209       break;
210    }
211
212    return 1;
213 }
214
215 /*
216  * Prune File records from the database. For any Job which
217  * is older than the retention period, we unconditionally delete
218  * all File records for that Job.  This is simple enough that no
219  * temporary tables are needed. We simply make an in memory list of
220  * the JobIds meeting the prune conditions, then delete all File records
221  * pointing to each of those JobIds.
222  *
223  * This routine assumes you want the pruning to be done. All checking
224  *  must be done before calling this routine.
225  */
226 int prune_files(UAContext *ua, CLIENT *client)
227 {
228    struct s_file_del_ctx del;
229    POOLMEM *query = get_pool_memory(PM_MESSAGE);
230    int i;
231    utime_t now, period;
232    CLIENT_DBR cr;
233    char ed1[50], ed2[50];
234
235    db_lock(ua->db);
236    memset(&cr, 0, sizeof(cr));
237    memset(&del, 0, sizeof(del));
238    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
239    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
240       db_unlock(ua->db);
241       return 0;
242    }
243
244    period = client->FileRetention;
245    now = (utime_t)time(NULL);
246        
247    /* Select Jobs -- for counting */
248    Mmsg(query, select_job, edit_uint64(now - period, ed1), cr.ClientId);
249    Dmsg1(050, "select sql=%s\n", query);
250    if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
251       if (ua->verbose) {
252          bsendmsg(ua, "%s", db_strerror(ua->db));
253       }
254       Dmsg0(050, "Count failed\n");
255       goto bail_out;
256    }
257       
258    if (del.tot_ids == 0) {
259       if (ua->verbose) {
260          bsendmsg(ua, _("No Files found to prune.\n"));
261       }
262       goto bail_out;
263    }
264
265    if (del.tot_ids < MAX_DEL_LIST_LEN) {
266       del.max_ids = del.tot_ids + 1;
267    } else {
268       del.max_ids = MAX_DEL_LIST_LEN; 
269    }
270    del.tot_ids = 0;
271
272    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
273
274    /* Now process same set but making a delete list */
275    db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
276
277    for (i=0; i < del.num_ids; i++) {
278       struct s_count_ctx cnt;
279       Dmsg1(050, "Delete JobId=%d\n", del.JobId[i]);
280       Mmsg(query, cnt_File, del.JobId[i]);
281       cnt.count = 0;
282       db_sql_query(ua->db, query, count_handler, (void *)&cnt);
283       del.tot_ids += cnt.count;
284       Mmsg(query, del_File, del.JobId[i]);
285       db_sql_query(ua->db, query, NULL, (void *)NULL);
286       /* 
287        * Now mark Job as having files purged. This is necessary to
288        * avoid having too many Jobs to process in future prunings. If
289        * we don't do this, the number of JobId's in our in memory list
290        * could grow very large.
291        */
292       Mmsg(query, upd_Purged, del.JobId[i]);
293       db_sql_query(ua->db, query, NULL, (void *)NULL);
294       Dmsg1(050, "Del sql=%s\n", query);
295    }
296    edit_uint64_with_commas(del.tot_ids, ed1);
297    edit_uint64_with_commas(del.num_ids, ed2);
298    bsendmsg(ua, _("Pruned %s Files from %s Jobs for client %s from catalog.\n"), 
299       ed1, ed2, client->hdr.name);
300    
301 bail_out:
302    db_unlock(ua->db);
303    if (del.JobId) {
304       free(del.JobId);
305    }
306    free_pool_memory(query);
307    return 1;
308 }
309
310
311 static void drop_temp_tables(UAContext *ua) 
312 {
313    int i;
314    for (i=0; drop_deltabs[i]; i++) {
315       db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
316    }
317 }
318
319 static int create_temp_tables(UAContext *ua) 
320 {
321    int i;
322    /* Create temp tables and indicies */
323    for (i=0; create_deltabs[i]; i++) {
324       if (!db_sql_query(ua->db, create_deltabs[i], NULL, (void *)NULL)) {
325          bsendmsg(ua, "%s", db_strerror(ua->db));
326          Dmsg0(050, "create DelTables table failed\n");
327          return 0;
328       }
329    }
330    return 1;
331 }
332
333
334
335 /*
336  * Purging Jobs is a bit more complicated than purging Files
337  * because we delete Job records only if there is a more current
338  * backup of the FileSet. Otherwise, we keep the Job record.
339  * In other words, we never delete the only Job record that
340  * contains a current backup of a FileSet. This prevents the
341  * Volume from being recycled and destroying a current backup.
342  *
343  * For Verify Jobs, we do not delete the last InitCatalog.
344  *
345  * For Restore Jobs there are no restrictions.
346  */
347 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
348 {
349    struct s_job_del_ctx del;
350    struct s_count_ctx cnt;
351    POOLMEM *query = (char *)get_pool_memory(PM_MESSAGE);
352    int i;
353    utime_t now, period;
354    CLIENT_DBR cr;
355    char ed1[50];
356
357    db_lock(ua->db);
358    memset(&cr, 0, sizeof(cr));
359    memset(&del, 0, sizeof(del));
360    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
361    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
362       db_unlock(ua->db);
363       return 0;
364    }
365
366    period = client->JobRetention;
367    now = (utime_t)time(NULL);
368
369    /* Drop any previous temporary tables still there */
370    drop_temp_tables(ua);
371
372    /* Create temp tables and indicies */
373    if (!create_temp_tables(ua)) {
374       goto bail_out;
375    }
376
377    /* 
378     * Select all files that are older than the JobRetention period
379     *  and stuff them into the "DeletionCandidates" table.
380     */
381    edit_uint64(now - period, ed1);
382    Mmsg(query, insert_delcand, (char)JobType, ed1, cr.ClientId);
383    if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
384       if (ua->verbose) {
385          bsendmsg(ua, "%s", db_strerror(ua->db));
386       }
387       Dmsg0(050, "insert delcand failed\n");
388       goto bail_out;
389    }
390
391    /* Count Files to be deleted */
392    pm_strcpy(query, cnt_DelCand);
393    Dmsg1(100, "select sql=%s\n", query);
394    cnt.count = 0;
395    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
396       bsendmsg(ua, "%s", db_strerror(ua->db));
397       Dmsg0(050, "Count failed\n");
398       goto bail_out;
399    }
400       
401    if (cnt.count == 0) {
402       if (ua->verbose) {
403          bsendmsg(ua, _("No Jobs found to prune.\n"));
404       }
405       goto bail_out;
406    }
407
408    if (cnt.count < MAX_DEL_LIST_LEN) {
409       del.max_ids = cnt.count + 1;
410    } else {
411       del.max_ids = MAX_DEL_LIST_LEN; 
412    }
413    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
414    del.PurgedFiles = (char *)malloc(del.max_ids);
415
416    switch (JobType) {
417    case JT_BACKUP:
418       Mmsg(query, select_backup_del, ed1, ed1, cr.ClientId);
419       break;
420    case JT_RESTORE:
421       Mmsg(query, select_restore_del, ed1, ed1, cr.ClientId);
422       break;
423    case JT_VERIFY:
424       Mmsg(query, select_verify_del, ed1, ed1, cr.ClientId);
425       break;
426    case JT_ADMIN:
427       Mmsg(query, select_admin_del, ed1, ed1, cr.ClientId);
428       break;
429    }
430    if (!db_sql_query(ua->db, query, job_delete_handler, (void *)&del)) {
431       bsendmsg(ua, "%s", db_strerror(ua->db));
432    }
433
434    /* 
435     * OK, now we have the list of JobId's to be pruned, first check
436     * if the Files have been purged, if not, purge (delete) them.
437     * Then delete the Job entry, and finally and JobMedia records.
438     */
439    for (i=0; i < del.num_ids; i++) {
440       Dmsg1(050, "Delete JobId=%d\n", del.JobId[i]);
441       if (!del.PurgedFiles[i]) {
442          Mmsg(query, del_File, del.JobId[i]);
443          if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
444             bsendmsg(ua, "%s", db_strerror(ua->db));
445          }
446          Dmsg1(050, "Del sql=%s\n", query);
447       }
448
449       Mmsg(query, del_Job, del.JobId[i]);
450       if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
451          bsendmsg(ua, "%s", db_strerror(ua->db));
452       }
453       Dmsg1(050, "Del sql=%s\n", query);
454
455       Mmsg(query, del_JobMedia, del.JobId[i]);
456       if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
457          bsendmsg(ua, "%s", db_strerror(ua->db));
458       }
459       Dmsg1(050, "Del sql=%s\n", query);
460    }
461    bsendmsg(ua, _("Pruned %d %s for client %s from catalog.\n"), del.num_ids,
462       del.num_ids==1?_("Job"):_("Jobs"), client->hdr.name);
463    
464 bail_out:
465    drop_temp_tables(ua);
466    db_unlock(ua->db);
467    if (del.JobId) {
468       free(del.JobId);
469    }
470    if (del.PurgedFiles) {
471       free(del.PurgedFiles);
472    }
473    free_pool_memory(query);
474    return 1;
475 }
476
477 /*
478  * Prune a given Volume
479  */
480 int prune_volume(UAContext *ua, MEDIA_DBR *mr)
481 {
482    POOLMEM *query = (char *)get_pool_memory(PM_MESSAGE);
483    struct s_count_ctx cnt;
484    struct s_file_del_ctx del;
485    int i, stat = 0;
486    JOB_DBR jr;
487    utime_t now, period;
488
489    db_lock(ua->db);
490    memset(&jr, 0, sizeof(jr));
491    memset(&del, 0, sizeof(del));
492
493    /*
494     * Find out how many Jobs remain on this Volume by 
495     *  counting the JobMedia records.
496     */
497    cnt.count = 0;
498    Mmsg(query, cnt_JobMedia, mr->MediaId);
499    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
500       bsendmsg(ua, "%s", db_strerror(ua->db));
501       Dmsg0(050, "Count failed\n");
502       goto bail_out;
503    }
504       
505    if (cnt.count == 0) {
506       if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
507          bsendmsg(ua, "There are no Jobs associated with Volume \"%s\". Marking it purged.\n",
508             mr->VolumeName);
509       }
510       stat = mark_media_purged(ua, mr);
511       goto bail_out;
512    }
513
514    if (cnt.count < MAX_DEL_LIST_LEN) {
515       del.max_ids = cnt.count + 1;
516    } else {
517       del.max_ids = MAX_DEL_LIST_LEN; 
518    }
519
520    /* 
521     * Now get a list of JobIds for Jobs written to this Volume
522     *   Could optimize here by adding JobTDate > (now - period).
523     */
524    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
525    Mmsg(query, sel_JobMedia, mr->MediaId);
526    if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
527       if (ua->verbose) {
528          bsendmsg(ua, "%s", db_strerror(ua->db));
529       }
530       Dmsg0(050, "Count failed\n");
531       goto bail_out;
532    }
533
534    /* Use Volume Retention to prune Jobs and their Files */
535    period = mr->VolRetention;
536    now = (utime_t)time(NULL);
537
538    Dmsg3(200, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
539       (int)(now-period));
540
541    for (i=0; i < del.num_ids; i++) {
542       jr.JobId = del.JobId[i];
543       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
544          continue;
545       }
546       Dmsg2(200, "Looking at %s JobTdate=%d\n", jr.Job, (int)jr.JobTDate);
547       if (jr.JobTDate >= (now - period)) {
548          continue;
549       }
550       Dmsg2(200, "Delete JobId=%d Job=%s\n", del.JobId[i], jr.Job);
551       Mmsg(query, del_File, del.JobId[i]);
552       db_sql_query(ua->db, query, NULL, (void *)NULL);
553       Mmsg(query, del_Job, del.JobId[i]);
554       db_sql_query(ua->db, query, NULL, (void *)NULL);
555       Mmsg(query, del_JobMedia, del.JobId[i]);
556       db_sql_query(ua->db, query, NULL, (void *)NULL);
557       Dmsg1(050, "Del sql=%s\n", query);
558       del.num_del++;
559    }
560    if (del.JobId) {
561       free(del.JobId);
562    }
563    if (ua->verbose && del.num_del != 0) {
564       bsendmsg(ua, _("Pruned %d %s on Volume \"%s\" from catalog.\n"), del.num_del,
565          del.num_del == 1 ? "Job" : "Jobs", mr->VolumeName);
566    }
567
568    /* If purged, mark it so */
569    if (del.num_ids == del.num_del) {
570       Dmsg0(200, "Volume is purged.\n");
571       stat = mark_media_purged(ua, mr);
572    }
573
574 bail_out:
575    db_unlock(ua->db);
576    free_pool_memory(query);
577    return stat;
578 }