]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_prune.c
- First cut of new Python implementation.
[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-2005 Kern Sibbald
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 = str_to_int64(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), 
249         edit_int64(cr.ClientId, ed1));
250    Dmsg1(050, "select sql=%s\n", query);
251    if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
252       if (ua->verbose) {
253          bsendmsg(ua, "%s", db_strerror(ua->db));
254       }
255       Dmsg0(050, "Count failed\n");
256       goto bail_out;
257    }
258
259    if (del.tot_ids == 0) {
260       if (ua->verbose) {
261          bsendmsg(ua, _("No Files found to prune.\n"));
262       }
263       goto bail_out;
264    }
265
266    if (del.tot_ids < MAX_DEL_LIST_LEN) {
267       del.max_ids = del.tot_ids + 1;
268    } else {
269       del.max_ids = MAX_DEL_LIST_LEN;
270    }
271    del.tot_ids = 0;
272
273    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
274
275    /* Now process same set but making a delete list */
276    db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
277
278    for (i=0; i < del.num_ids; i++) {
279       struct s_count_ctx cnt;
280       Dmsg1(050, "Delete JobId=%u\n", del.JobId[i]);
281       Mmsg(query, cnt_File, edit_int64(del.JobId[i], ed1));
282       cnt.count = 0;
283       db_sql_query(ua->db, query, count_handler, (void *)&cnt);
284       del.tot_ids += cnt.count;
285       Mmsg(query, del_File, edit_int64(del.JobId[i], ed1));
286       db_sql_query(ua->db, query, NULL, (void *)NULL);
287       /*
288        * Now mark Job as having files purged. This is necessary to
289        * avoid having too many Jobs to process in future prunings. If
290        * we don't do this, the number of JobId's in our in memory list
291        * could grow very large.
292        */
293       Mmsg(query, upd_Purged, edit_int64(del.JobId[i], ed1));
294       db_sql_query(ua->db, query, NULL, (void *)NULL);
295       Dmsg1(050, "Del sql=%s\n", query);
296    }
297    edit_uint64_with_commas(del.tot_ids, ed1);
298    edit_uint64_with_commas(del.num_ids, ed2);
299    bsendmsg(ua, _("Pruned %s Files from %s Jobs for client %s from catalog.\n"),
300       ed1, ed2, client->hdr.name);
301
302 bail_out:
303    db_unlock(ua->db);
304    if (del.JobId) {
305       free(del.JobId);
306    }
307    free_pool_memory(query);
308    return 1;
309 }
310
311
312 static void drop_temp_tables(UAContext *ua)
313 {
314    int i;
315    for (i=0; drop_deltabs[i]; i++) {
316       db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
317    }
318 }
319
320 static int create_temp_tables(UAContext *ua)
321 {
322    int i;
323    /* Create temp tables and indicies */
324    for (i=0; create_deltabs[i]; i++) {
325       if (!db_sql_query(ua->db, create_deltabs[i], NULL, (void *)NULL)) {
326          bsendmsg(ua, "%s", db_strerror(ua->db));
327          Dmsg0(050, "create DelTables table failed\n");
328          return 0;
329       }
330    }
331    return 1;
332 }
333
334
335
336 /*
337  * Purging Jobs is a bit more complicated than purging Files
338  * because we delete Job records only if there is a more current
339  * backup of the FileSet. Otherwise, we keep the Job record.
340  * In other words, we never delete the only Job record that
341  * contains a current backup of a FileSet. This prevents the
342  * Volume from being recycled and destroying a current backup.
343  *
344  * For Verify Jobs, we do not delete the last InitCatalog.
345  *
346  * For Restore Jobs there are no restrictions.
347  */
348 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
349 {
350    struct s_job_del_ctx del;
351    struct s_count_ctx cnt;
352    POOLMEM *query = (char *)get_pool_memory(PM_MESSAGE);
353    int i;
354    utime_t now, period;
355    CLIENT_DBR cr;
356    char ed1[50], ed2[50];
357
358    db_lock(ua->db);
359    memset(&cr, 0, sizeof(cr));
360    memset(&del, 0, sizeof(del));
361    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
362    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
363       db_unlock(ua->db);
364       return 0;
365    }
366
367    period = client->JobRetention;
368    now = (utime_t)time(NULL);
369
370    /* Drop any previous temporary tables still there */
371    drop_temp_tables(ua);
372
373    /* Create temp tables and indicies */
374    if (!create_temp_tables(ua)) {
375       goto bail_out;
376    }
377
378    /*
379     * Select all files that are older than the JobRetention period
380     *  and stuff them into the "DeletionCandidates" table.
381     */
382    edit_uint64(now - period, ed1);
383    Mmsg(query, insert_delcand, (char)JobType, ed1, 
384         edit_int64(cr.ClientId, ed2));
385    if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
386       if (ua->verbose) {
387          bsendmsg(ua, "%s", db_strerror(ua->db));
388       }
389       Dmsg0(050, "insert delcand failed\n");
390       goto bail_out;
391    }
392
393    /* Count Files to be deleted */
394    pm_strcpy(query, cnt_DelCand);
395    Dmsg1(100, "select sql=%s\n", query);
396    cnt.count = 0;
397    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
398       bsendmsg(ua, "%s", db_strerror(ua->db));
399       Dmsg0(050, "Count failed\n");
400       goto bail_out;
401    }
402
403    if (cnt.count == 0) {
404       if (ua->verbose) {
405          bsendmsg(ua, _("No Jobs found to prune.\n"));
406       }
407       goto bail_out;
408    }
409
410    if (cnt.count < MAX_DEL_LIST_LEN) {
411       del.max_ids = cnt.count + 1;
412    } else {
413       del.max_ids = MAX_DEL_LIST_LEN;
414    }
415    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
416    del.PurgedFiles = (char *)malloc(del.max_ids);
417
418    /* ed1 = JobTDate */
419    edit_int64(cr.ClientId, ed2);
420    switch (JobType) {
421    case JT_BACKUP:
422       Mmsg(query, select_backup_del, ed1, ed1, ed2);
423       break;
424    case JT_RESTORE:
425       Mmsg(query, select_restore_del, ed1, ed1, ed2);
426       break;
427    case JT_VERIFY:
428       Mmsg(query, select_verify_del, ed1, ed1, ed2);
429       break;
430    case JT_ADMIN:
431       Mmsg(query, select_admin_del, ed1, ed1, ed2);
432       break;
433    }
434    if (!db_sql_query(ua->db, query, job_delete_handler, (void *)&del)) {
435       bsendmsg(ua, "%s", db_strerror(ua->db));
436    }
437
438    /*
439     * OK, now we have the list of JobId's to be pruned, first check
440     * if the Files have been purged, if not, purge (delete) them.
441     * Then delete the Job entry, and finally and JobMedia records.
442     */
443    for (i=0; i < del.num_ids; i++) {
444       Dmsg1(050, "Delete JobId=%d\n", del.JobId[i]);
445       if (!del.PurgedFiles[i]) {
446          Mmsg(query, del_File, edit_int64(del.JobId[i], ed1));
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
453       Mmsg(query, del_Job, edit_int64(del.JobId[i], ed1));
454       if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
455          bsendmsg(ua, "%s", db_strerror(ua->db));
456       }
457       Dmsg1(050, "Del sql=%s\n", query);
458
459       Mmsg(query, del_JobMedia, edit_int64(del.JobId[i], ed1));
460       if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
461          bsendmsg(ua, "%s", db_strerror(ua->db));
462       }
463       Dmsg1(050, "Del sql=%s\n", query);
464    }
465    bsendmsg(ua, _("Pruned %d %s for client %s from catalog.\n"), del.num_ids,
466       del.num_ids==1?_("Job"):_("Jobs"), client->hdr.name);
467
468 bail_out:
469    drop_temp_tables(ua);
470    db_unlock(ua->db);
471    if (del.JobId) {
472       free(del.JobId);
473    }
474    if (del.PurgedFiles) {
475       free(del.PurgedFiles);
476    }
477    free_pool_memory(query);
478    return 1;
479 }
480
481 /*
482  * Prune a given Volume
483  */
484 int prune_volume(UAContext *ua, MEDIA_DBR *mr)
485 {
486    POOLMEM *query = (char *)get_pool_memory(PM_MESSAGE);
487    struct s_count_ctx cnt;
488    struct s_file_del_ctx del;
489    int i, stat = 0;
490    JOB_DBR jr;
491    utime_t now, period;
492    char ed1[50];
493
494    db_lock(ua->db);
495    memset(&jr, 0, sizeof(jr));
496    memset(&del, 0, sizeof(del));
497
498    /*
499     * Find out how many Jobs remain on this Volume by
500     *  counting the JobMedia records.
501     */
502    cnt.count = 0;
503    Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
504    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
505       bsendmsg(ua, "%s", db_strerror(ua->db));
506       Dmsg0(050, "Count failed\n");
507       goto bail_out;
508    }
509
510    if (cnt.count == 0) {
511       if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
512          bsendmsg(ua, "There are no Jobs associated with Volume \"%s\". Marking it purged.\n",
513             mr->VolumeName);
514       }
515       stat = mark_media_purged(ua, mr);
516       goto bail_out;
517    }
518
519    if (cnt.count < MAX_DEL_LIST_LEN) {
520       del.max_ids = cnt.count + 1;
521    } else {
522       del.max_ids = MAX_DEL_LIST_LEN;
523    }
524
525    /*
526     * Now get a list of JobIds for Jobs written to this Volume
527     *   Could optimize here by adding JobTDate > (now - period).
528     */
529    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
530    Mmsg(query, sel_JobMedia, edit_int64(mr->MediaId, ed1));
531    if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
532       if (ua->verbose) {
533          bsendmsg(ua, "%s", db_strerror(ua->db));
534       }
535       Dmsg0(050, "Count failed\n");
536       goto bail_out;
537    }
538
539    /* Use Volume Retention to prune Jobs and their Files */
540    period = mr->VolRetention;
541    now = (utime_t)time(NULL);
542
543    Dmsg3(200, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
544       (int)(now-period));
545
546    for (i=0; i < del.num_ids; i++) {
547       jr.JobId = del.JobId[i];
548       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
549          continue;
550       }
551       Dmsg2(200, "Looking at %s JobTdate=%d\n", jr.Job, (int)jr.JobTDate);
552       if (jr.JobTDate >= (now - period)) {
553          continue;
554       }
555       Dmsg2(200, "Delete JobId=%d Job=%s\n", del.JobId[i], jr.Job);
556       Mmsg(query, del_File, edit_int64(del.JobId[i], ed1));
557       db_sql_query(ua->db, query, NULL, (void *)NULL);
558       Mmsg(query, del_Job, edit_int64(del.JobId[i], ed1));
559       db_sql_query(ua->db, query, NULL, (void *)NULL);
560       Mmsg(query, del_JobMedia, edit_int64(del.JobId[i], 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    /* If purged, mark it so */
574    if (del.num_ids == del.num_del) {
575       Dmsg0(200, "Volume is purged.\n");
576       stat = mark_media_purged(ua, mr);
577    }
578
579 bail_out:
580    db_unlock(ua->db);
581    free_pool_memory(query);
582    return stat;
583 }