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