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