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