]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_prune.c
kes Print JobIds to be migrated in Job Report.
[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 (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
178          return false;
179       }
180       prune_volume(ua, &mr);
181       return true;
182    default:
183       break;
184    }
185
186    return true;
187 }
188
189 /*
190  * Prune File records from the database. For any Job which
191  * is older than the retention period, we unconditionally delete
192  * all File records for that Job.  This is simple enough that no
193  * temporary tables are needed. We simply make an in memory list of
194  * the JobIds meeting the prune conditions, then delete all File records
195  * pointing to each of those JobIds.
196  *
197  * This routine assumes you want the pruning to be done. All checking
198  *  must be done before calling this routine.
199  */
200 int prune_files(UAContext *ua, CLIENT *client)
201 {
202    struct s_file_del_ctx del;
203    POOLMEM *query = get_pool_memory(PM_MESSAGE);
204    int i;
205    utime_t now, period;
206    CLIENT_DBR cr;
207    char ed1[50], ed2[50];
208
209    db_lock(ua->db);
210    memset(&cr, 0, sizeof(cr));
211    memset(&del, 0, sizeof(del));
212    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
213    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
214       db_unlock(ua->db);
215       return 0;
216    }
217
218    period = client->FileRetention;
219    now = (utime_t)time(NULL);
220
221    /* Select Jobs -- for counting */
222    Mmsg(query, select_job, edit_uint64(now - period, ed1), 
223         edit_int64(cr.ClientId, ed2));
224    Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, (uint32_t)period, query);
225    if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
226       if (ua->verbose) {
227          bsendmsg(ua, "%s", db_strerror(ua->db));
228       }
229       Dmsg0(050, "Count failed\n");
230       goto bail_out;
231    }
232
233    if (del.tot_ids == 0) {
234       if (ua->verbose) {
235          bsendmsg(ua, _("No Files found to prune.\n"));
236       }
237       goto bail_out;
238    }
239
240    if (del.tot_ids < MAX_DEL_LIST_LEN) {
241       del.max_ids = del.tot_ids + 1;
242    } else {
243       del.max_ids = MAX_DEL_LIST_LEN;
244    }
245    del.tot_ids = 0;
246
247    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
248
249    /* Now process same set but making a delete list */
250    db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
251
252    for (i=0; i < del.num_ids; i++) {
253       purge_files_from_job(ua, del.JobId[i]);
254    }
255    edit_uint64_with_commas(del.num_ids, ed1);
256    bsendmsg(ua, _("Pruned Files from %s Jobs for client %s from catalog.\n"),
257       ed1, client->hdr.name);
258
259 bail_out:
260    db_unlock(ua->db);
261    if (del.JobId) {
262       free(del.JobId);
263    }
264    free_pool_memory(query);
265    return 1;
266 }
267
268
269 static void drop_temp_tables(UAContext *ua)
270 {
271    int i;
272    for (i=0; drop_deltabs[i]; i++) {
273       db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
274    }
275 }
276
277 static bool create_temp_tables(UAContext *ua)
278 {
279    int i;
280    /* Create temp tables and indicies */
281    for (i=0; create_deltabs[i]; i++) {
282       if (!db_sql_query(ua->db, create_deltabs[i], NULL, (void *)NULL)) {
283          bsendmsg(ua, "%s", db_strerror(ua->db));
284          Dmsg0(050, "create DelTables table failed\n");
285          return false;
286       }
287    }
288    return true;
289 }
290
291
292
293 /*
294  * Pruning Jobs is a bit more complicated than purging Files
295  * because we delete Job records only if there is a more current
296  * backup of the FileSet. Otherwise, we keep the Job record.
297  * In other words, we never delete the only Job record that
298  * contains a current backup of a FileSet. This prevents the
299  * Volume from being recycled and destroying a current backup.
300  *
301  * For Verify Jobs, we do not delete the last InitCatalog.
302  *
303  * For Restore Jobs there are no restrictions.
304  */
305 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
306 {
307    struct s_job_del_ctx del;
308    struct s_count_ctx cnt;
309    POOLMEM *query = get_pool_memory(PM_MESSAGE);
310    int i;
311    utime_t now, period;
312    CLIENT_DBR cr;
313    char ed1[50], ed2[50];
314
315    db_lock(ua->db);
316    memset(&cr, 0, sizeof(cr));
317    memset(&del, 0, sizeof(del));
318    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
319    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
320       db_unlock(ua->db);
321       return 0;
322    }
323
324    period = client->JobRetention;
325    now = (utime_t)time(NULL);
326
327    /* Drop any previous temporary tables still there */
328    drop_temp_tables(ua);
329
330    /* Create temp tables and indicies */
331    if (!create_temp_tables(ua)) {
332       goto bail_out;
333    }
334
335    /*
336     * Select all files that are older than the JobRetention period
337     *  and stuff them into the "DeletionCandidates" table.
338     */
339    edit_uint64(now - period, ed1);
340    Mmsg(query, insert_delcand, (char)JobType, ed1, 
341         edit_int64(cr.ClientId, ed2));
342    if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
343       if (ua->verbose) {
344          bsendmsg(ua, "%s", db_strerror(ua->db));
345       }
346       Dmsg0(050, "insert delcand failed\n");
347       goto bail_out;
348    }
349
350    /* Count Files to be deleted */
351    pm_strcpy(query, cnt_DelCand);
352    Dmsg1(100, "select sql=%s\n", query);
353    cnt.count = 0;
354    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
355       bsendmsg(ua, "%s", db_strerror(ua->db));
356       Dmsg0(050, "Count failed\n");
357       goto bail_out;
358    }
359
360    if (cnt.count == 0) {
361       if (ua->verbose) {
362          bsendmsg(ua, _("No Jobs found to prune.\n"));
363       }
364       goto bail_out;
365    }
366
367    if (cnt.count < MAX_DEL_LIST_LEN) {
368       del.max_ids = cnt.count + 1;
369    } else {
370       del.max_ids = MAX_DEL_LIST_LEN;
371    }
372    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
373    del.PurgedFiles = (char *)malloc(del.max_ids);
374
375    /* ed1 = JobTDate */
376    edit_int64(cr.ClientId, ed2);
377    switch (JobType) {
378    case JT_BACKUP:
379       Mmsg(query, select_backup_del, ed1, ed1, ed2);
380       break;
381    case JT_RESTORE:
382       Mmsg(query, select_restore_del, ed1, ed1, ed2);
383       break;
384    case JT_VERIFY:
385       Mmsg(query, select_verify_del, ed1, ed1, ed2);
386       break;
387    case JT_ADMIN:
388       Mmsg(query, select_admin_del, ed1, ed1, ed2);
389       break;
390    case JT_MIGRATE:
391       Mmsg(query, select_migrate_del, ed1, ed1, ed2);
392       break;
393    }
394    if (!db_sql_query(ua->db, query, job_delete_handler, (void *)&del)) {
395       bsendmsg(ua, "%s", db_strerror(ua->db));
396    }
397
398    /*
399     * OK, now we have the list of JobId's to be pruned, first check
400     * if the Files have been purged, if not, purge (delete) them.
401     * Then delete the Job entry, and finally and JobMedia records.
402     */
403    for (i=0; i < del.num_ids; i++) {
404       if (!del.PurgedFiles[i]) {
405          purge_files_from_job(ua, del.JobId[i]);
406       }
407       purge_job_from_catalog(ua, del.JobId[i]);
408    }
409    bsendmsg(ua, _("Pruned %d %s for client %s from catalog.\n"), del.num_ids,
410       del.num_ids==1?_("Job"):_("Jobs"), client->hdr.name);
411
412 bail_out:
413    drop_temp_tables(ua);
414    db_unlock(ua->db);
415    if (del.JobId) {
416       free(del.JobId);
417    }
418    if (del.PurgedFiles) {
419       free(del.PurgedFiles);
420    }
421    free_pool_memory(query);
422    return 1;
423 }
424
425 /*
426  * Prune a given Volume
427  */
428 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
429 {
430    POOLMEM *query = get_pool_memory(PM_MESSAGE);
431    struct s_count_ctx cnt;
432    struct s_file_del_ctx del;
433    int i;          
434    bool ok = false;
435    JOB_DBR jr;
436    utime_t now, period;
437    char ed1[50];
438
439    if (mr->Enabled == 2) {
440       return false;                   /* Cannot prune archived volumes */
441    }
442
443    db_lock(ua->db);
444    memset(&jr, 0, sizeof(jr));
445    memset(&del, 0, sizeof(del));
446
447    /*
448     * Find out how many Jobs remain on this Volume by
449     *  counting the JobMedia records.
450     */
451    cnt.count = 0;
452    Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
453    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
454       bsendmsg(ua, "%s", db_strerror(ua->db));
455       Dmsg0(050, "Count failed\n");
456       goto bail_out;
457    }
458
459    if (cnt.count == 0) {
460       /* Don't mark appendable volume as purged */
461       if (strcmp(mr->VolStatus, "Append") == 0 && verbose) {
462          bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Prune not needed.\n"),
463             mr->VolumeName);
464          ok = true;
465          goto bail_out;
466       }
467       /* If volume not already purged, do so */
468       if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
469          bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
470             mr->VolumeName);
471       }
472       ok = mark_media_purged(ua, mr);
473       goto bail_out;
474    }
475
476    if (cnt.count < MAX_DEL_LIST_LEN) {
477       del.max_ids = cnt.count + 1;
478    } else {
479       del.max_ids = MAX_DEL_LIST_LEN;
480    }
481
482    /*
483     * Now get a list of JobIds for Jobs written to this Volume
484     *   Could optimize here by adding JobTDate > (now - period).
485     */
486    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
487    Mmsg(query, sel_JobMedia, edit_int64(mr->MediaId, ed1));
488    if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
489       if (ua->verbose) {
490          bsendmsg(ua, "%s", db_strerror(ua->db));
491       }
492       Dmsg0(050, "Count failed\n");
493       goto bail_out;
494    }
495
496    /* Use Volume Retention to prune Jobs and their Files */
497    period = mr->VolRetention;
498    now = (utime_t)time(NULL);
499
500    Dmsg3(200, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
501       (int)(now-period));
502
503    for (i=0; i < del.num_ids; i++) {
504       jr.JobId = del.JobId[i];
505       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
506          continue;
507       }
508       Dmsg2(200, "Looking at %s JobTdate=%d\n", jr.Job, (int)jr.JobTDate);
509       if (jr.JobTDate >= (now - period)) {
510          continue;
511       }
512       purge_files_from_job(ua, del.JobId[i]);
513       purge_job_from_catalog(ua, del.JobId[i]);
514       del.num_del++;
515    }
516    if (del.JobId) {
517       free(del.JobId);
518    }
519    if (ua->verbose && del.num_del != 0) {
520       bsendmsg(ua, _("Pruned %d %s on Volume \"%s\" from catalog.\n"), del.num_del,
521          del.num_del == 1 ? "Job" : "Jobs", mr->VolumeName);
522    }
523
524    /*
525     * Find out how many Jobs remain on this Volume by
526     *  counting the JobMedia records.
527     */
528    cnt.count = 0;
529    Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
530    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
531       bsendmsg(ua, "%s", db_strerror(ua->db));
532       Dmsg0(050, "Count failed\n");
533       goto bail_out;
534    }
535    if (cnt.count == 0) {
536       Dmsg0(200, "Volume is purged.\n");
537       ok = mark_media_purged(ua, mr);
538    }
539
540 bail_out:
541    db_unlock(ua->db);
542    free_pool_memory(query);
543    return ok;
544 }