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