]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_prune.c
ebl Try to fix bug on RunScript {} resource parsing
[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  * Purging 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    }
391    if (!db_sql_query(ua->db, query, job_delete_handler, (void *)&del)) {
392       bsendmsg(ua, "%s", db_strerror(ua->db));
393    }
394
395    /*
396     * OK, now we have the list of JobId's to be pruned, first check
397     * if the Files have been purged, if not, purge (delete) them.
398     * Then delete the Job entry, and finally and JobMedia records.
399     */
400    for (i=0; i < del.num_ids; i++) {
401       if (!del.PurgedFiles[i]) {
402          purge_files_from_job(ua, del.JobId[i]);
403       }
404       purge_job_from_catalog(ua, del.JobId[i]);
405    }
406    bsendmsg(ua, _("Pruned %d %s for client %s from catalog.\n"), del.num_ids,
407       del.num_ids==1?_("Job"):_("Jobs"), client->hdr.name);
408
409 bail_out:
410    drop_temp_tables(ua);
411    db_unlock(ua->db);
412    if (del.JobId) {
413       free(del.JobId);
414    }
415    if (del.PurgedFiles) {
416       free(del.PurgedFiles);
417    }
418    free_pool_memory(query);
419    return 1;
420 }
421
422 /*
423  * Prune a given Volume
424  */
425 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
426 {
427    POOLMEM *query = get_pool_memory(PM_MESSAGE);
428    struct s_count_ctx cnt;
429    struct s_file_del_ctx del;
430    int i;          
431    bool ok = false;
432    JOB_DBR jr;
433    utime_t now, period;
434    char ed1[50];
435
436    if (mr->Enabled == 2) {
437       return false;                   /* Cannot prune archived volumes */
438    }
439
440    db_lock(ua->db);
441    memset(&jr, 0, sizeof(jr));
442    memset(&del, 0, sizeof(del));
443
444    /*
445     * Find out how many Jobs remain on this Volume by
446     *  counting the JobMedia records.
447     */
448    cnt.count = 0;
449    Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
450    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
451       bsendmsg(ua, "%s", db_strerror(ua->db));
452       Dmsg0(050, "Count failed\n");
453       goto bail_out;
454    }
455
456    if (cnt.count == 0) {
457       /* Don't mark appendable volume as purged */
458       if (strcmp(mr->VolStatus, "Append") == 0 && verbose) {
459          bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Prune not needed.\n"),
460             mr->VolumeName);
461          ok = true;
462          goto bail_out;
463       }
464       /* If volume not already purged, do so */
465       if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
466          bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
467             mr->VolumeName);
468       }
469       ok = mark_media_purged(ua, mr);
470       goto bail_out;
471    }
472
473    if (cnt.count < MAX_DEL_LIST_LEN) {
474       del.max_ids = cnt.count + 1;
475    } else {
476       del.max_ids = MAX_DEL_LIST_LEN;
477    }
478
479    /*
480     * Now get a list of JobIds for Jobs written to this Volume
481     *   Could optimize here by adding JobTDate > (now - period).
482     */
483    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
484    Mmsg(query, sel_JobMedia, edit_int64(mr->MediaId, ed1));
485    if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
486       if (ua->verbose) {
487          bsendmsg(ua, "%s", db_strerror(ua->db));
488       }
489       Dmsg0(050, "Count failed\n");
490       goto bail_out;
491    }
492
493    /* Use Volume Retention to prune Jobs and their Files */
494    period = mr->VolRetention;
495    now = (utime_t)time(NULL);
496
497    Dmsg3(200, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
498       (int)(now-period));
499
500    for (i=0; i < del.num_ids; i++) {
501       jr.JobId = del.JobId[i];
502       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
503          continue;
504       }
505       Dmsg2(200, "Looking at %s JobTdate=%d\n", jr.Job, (int)jr.JobTDate);
506       if (jr.JobTDate >= (now - period)) {
507          continue;
508       }
509       purge_files_from_job(ua, del.JobId[i]);
510       purge_job_from_catalog(ua, del.JobId[i]);
511       del.num_del++;
512    }
513    if (del.JobId) {
514       free(del.JobId);
515    }
516    if (ua->verbose && del.num_del != 0) {
517       bsendmsg(ua, _("Pruned %d %s on Volume \"%s\" from catalog.\n"), del.num_del,
518          del.num_del == 1 ? "Job" : "Jobs", mr->VolumeName);
519    }
520
521    /*
522     * Find out how many Jobs remain on this Volume by
523     *  counting the JobMedia records.
524     */
525    cnt.count = 0;
526    Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
527    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
528       bsendmsg(ua, "%s", db_strerror(ua->db));
529       Dmsg0(050, "Count failed\n");
530       goto bail_out;
531    }
532    if (cnt.count == 0) {
533       Dmsg0(200, "Volume is purged.\n");
534       ok = mark_media_purged(ua, mr);
535    }
536
537 bail_out:
538    db_unlock(ua->db);
539    free_pool_memory(query);
540    return ok;
541 }