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