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