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