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