]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_prune.c
start to tweak prune algo
[bacula/bacula] / bacula / src / dird / ua_prune.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2002-2009 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 and included
11    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 Kern Sibbald.
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  */
36
37 #include "bacula.h"
38 #include "dird.h"
39
40 /* Imported functions */
41
42 /* Forward referenced functions */
43
44 /*
45  * Called here to count entries to be deleted
46  */
47 int del_count_handler(void *ctx, int num_fields, char **row)
48 {
49    struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
50
51    if (row[0]) {
52       cnt->count = str_to_int64(row[0]);
53    } else {
54       cnt->count = 0;
55    }
56    return 0;
57 }
58
59
60 /*
61  * Called here to make in memory list of JobIds to be
62  *  deleted and the associated PurgedFiles flag.
63  *  The in memory list will then be transversed
64  *  to issue the SQL DELETE commands.  Note, the list
65  *  is allowed to get to MAX_DEL_LIST_LEN to limit the
66  *  maximum malloc'ed memory.
67  */
68 int job_delete_handler(void *ctx, int num_fields, char **row)
69 {
70    struct del_ctx *del = (struct del_ctx *)ctx;
71
72    if (del->num_ids == MAX_DEL_LIST_LEN) {
73       return 1;
74    }
75    if (del->num_ids == del->max_ids) {
76       del->max_ids = (del->max_ids * 3) / 2;
77       del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
78       del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
79    }
80    del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
81 // Dmsg2(60, "row=%d val=%d\n", del->num_ids, del->JobId[del->num_ids]);
82    del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[1]);
83    return 0;
84 }
85
86 int file_delete_handler(void *ctx, int num_fields, char **row)
87 {
88    struct del_ctx *del = (struct del_ctx *)ctx;
89
90    if (del->num_ids == MAX_DEL_LIST_LEN) {
91       return 1;
92    }
93    if (del->num_ids == del->max_ids) {
94       del->max_ids = (del->max_ids * 3) / 2;
95       del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
96          del->max_ids);
97    }
98    del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
99 // Dmsg2(150, "row=%d val=%d\n", del->num_ids-1, del->JobId[del->num_ids-1]);
100    return 0;
101 }
102
103 /*
104  *   Prune records from database
105  *
106  *    prune files (from) client=xxx [pool=yyy]
107  *    prune jobs (from) client=xxx [pool=yyy]
108  *    prune volume=xxx
109  *    prune stats
110  */
111 int prunecmd(UAContext *ua, const char *cmd)
112 {
113    DIRRES *dir;
114    CLIENT *client;
115    POOL *pool;
116    POOL_DBR pr;
117    MEDIA_DBR mr;
118    utime_t retention;
119    int kw;
120
121    static const char *keywords[] = {
122       NT_("Files"),
123       NT_("Jobs"),
124       NT_("Volume"),
125       NT_("Stats"),
126       NULL};
127
128    if (!open_client_db(ua)) {
129       return false;
130    }
131
132    /* First search args */
133    kw = find_arg_keyword(ua, keywords);
134    if (kw < 0 || kw > 3) {
135       /* no args, so ask user */
136       kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);
137    }
138
139    switch (kw) {
140    case 0:  /* prune files */
141       client = get_client_resource(ua);
142       if (find_arg_with_value(ua, "pool") >= 0) {
143          pool = get_pool_resource(ua);
144       } else {
145          pool = NULL;
146       }
147       /* Pool File Retention takes precedence over client File Retention */
148       if (pool && pool->FileRetention > 0) {
149          if (!confirm_retention(ua, &pool->FileRetention, "File")) {
150             return false;
151          }
152       } else if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
153          return false;
154       }
155       prune_files(ua, client, pool);
156       return true;
157    case 1:  /* prune jobs */
158       client = get_client_resource(ua);
159       if (find_arg_with_value(ua, "pool") >= 0) {
160          pool = get_pool_resource(ua);
161       } else {
162          pool = NULL;
163       }
164       /* Pool Job Retention takes precedence over client Job Retention */
165       if (pool && pool->JobRetention > 0) {
166          if (!confirm_retention(ua, &pool->JobRetention, "Job")) {
167             return false;
168          }
169       } else if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
170          return false;
171       }
172       /* ****FIXME**** allow user to select JobType */
173       prune_jobs(ua, client, pool, 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          ua->error_msg(_("Cannot prune Volume \"%s\" because it is archived.\n"),
181             mr.VolumeName);
182          return false;
183       }
184       if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
185          return false;
186       }
187       prune_volume(ua, &mr);
188       return true;
189    case 3:  /* prune stats */
190       dir = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
191       if (!dir->stats_retention) {
192          return false;
193       }
194       retention = dir->stats_retention;
195       if (!confirm_retention(ua, &retention, "Statistics")) {
196          return false;
197       }
198       prune_stats(ua, retention);
199       return true;
200    default:
201       break;
202    }
203
204    return true;
205 }
206
207 /* Prune Job stat records from the database. 
208  *
209  */
210 int prune_stats(UAContext *ua, utime_t retention)
211 {
212    char ed1[50];
213    POOL_MEM query(PM_MESSAGE);
214    utime_t now = (utime_t)time(NULL);
215
216    db_lock(ua->db);
217    Mmsg(query, "DELETE FROM JobHisto WHERE JobTDate < %s", 
218         edit_int64(now - retention, ed1));
219    db_sql_query(ua->db, query.c_str(), NULL, NULL);
220    db_unlock(ua->db);
221
222    ua->info_msg(_("Pruned Jobs from JobHisto catalog.\n"));
223
224    return true;
225 }
226
227 /*
228  * Prune File records from the database. For any Job which
229  * is older than the retention period, we unconditionally delete
230  * all File records for that Job.  This is simple enough that no
231  * temporary tables are needed. We simply make an in memory list of
232  * the JobIds meeting the prune conditions, then delete all File records
233  * pointing to each of those JobIds.
234  *
235  * This routine assumes you want the pruning to be done. All checking
236  *  must be done before calling this routine.
237  *
238  * Note: pool can possibly be NULL.
239  */
240 int prune_files(UAContext *ua, CLIENT *client, POOL *pool)
241 {
242    struct del_ctx del;
243    struct s_count_ctx cnt;
244    POOL_MEM query(PM_MESSAGE);
245    utime_t now, period;
246    CLIENT_DBR cr;
247    char ed1[50], ed2[50];
248
249    db_lock(ua->db);
250    memset(&cr, 0, sizeof(cr));
251    memset(&del, 0, sizeof(del));
252    bstrncpy(cr.Name, client->name(), sizeof(cr.Name));
253    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
254       db_unlock(ua->db);
255       return 0;
256    }
257
258    if (pool && pool->FileRetention > 0) {
259       period = pool->FileRetention;
260    } else {
261       period = client->FileRetention;
262    }
263    now = (utime_t)time(NULL);
264
265 //   edit_utime(now-period, ed1, sizeof(ed1));
266 //   Jmsg(ua->jcr, M_INFO, 0, _("Begin pruning Jobs older than %s secs.\n"), ed1);
267    Jmsg(ua->jcr, M_INFO, 0, _("Begin pruning Jobs.\n"));
268    /* Select Jobs -- for counting */ 
269    edit_int64(now - period, ed1);
270    Mmsg(query, count_select_job, ed1, edit_int64(cr.ClientId, ed2));
271    Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, 
272                (uint32_t)period, query.c_str());
273    cnt.count = 0;
274    if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
275       ua->error_msg("%s", db_strerror(ua->db));
276       Dmsg0(050, "Count failed\n");
277       goto bail_out;
278    }
279
280    if (cnt.count == 0) {
281       if (ua->verbose) {
282          ua->warning_msg(_("No Files found to prune.\n"));
283       }
284       goto bail_out;
285    }
286
287    if (cnt.count < MAX_DEL_LIST_LEN) {
288       del.max_ids = cnt.count + 1;
289    } else {
290       del.max_ids = MAX_DEL_LIST_LEN;
291    }
292    del.tot_ids = 0;
293
294    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
295
296    /* Now process same set but making a delete list */
297    Mmsg(query, select_job, edit_int64(now - period, ed1), 
298         edit_int64(cr.ClientId, ed2));
299    db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del);
300
301    purge_files_from_job_list(ua, del);
302
303    edit_uint64_with_commas(del.num_del, ed1);
304    ua->info_msg(_("Pruned Files from %s Jobs for client %s from catalog.\n"),
305       ed1, client->name());
306
307 bail_out:
308    db_unlock(ua->db);
309    if (del.JobId) {
310       free(del.JobId);
311    }
312    return 1;
313 }
314
315
316 static void drop_temp_tables(UAContext *ua)
317 {
318    int i;
319    for (i=0; drop_deltabs[i]; i++) {
320       db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
321    }
322 }
323
324 static bool create_temp_tables(UAContext *ua)
325 {
326    /* Create temp tables and indicies */
327    if (!db_sql_query(ua->db, create_deltabs[db_type], NULL, (void *)NULL)) {
328       ua->error_msg("%s", db_strerror(ua->db));
329       Dmsg0(050, "create DelTables table failed\n");
330       return false;
331    }
332    if (!db_sql_query(ua->db, create_delindex, NULL, (void *)NULL)) {
333        ua->error_msg("%s", db_strerror(ua->db));
334        Dmsg0(050, "create DelInx1 index failed\n");
335        return false;
336    }
337    return true;
338 }
339
340
341
342 /*
343  * Pruning Jobs is a bit more complicated than purging Files
344  * because we delete Job records only if there is a more current
345  * backup of the FileSet. Otherwise, we keep the Job record.
346  * In other words, we never delete the only Job record that
347  * contains a current backup of a FileSet. This prevents the
348  * Volume from being recycled and destroying a current backup.
349  *
350  * For Verify Jobs, we do not delete the last InitCatalog.
351  *
352  * For Restore Jobs there are no restrictions.
353  */
354 int prune_jobs(UAContext *ua, CLIENT *client, POOL *pool, int JobType)
355 {
356    struct del_ctx del;
357    POOL_MEM query(PM_MESSAGE);
358    utime_t now, period;
359    CLIENT_DBR cr;
360    char ed1[50], ed2[50];
361
362    db_lock(ua->db);
363    memset(&cr, 0, sizeof(cr));
364
365    bstrncpy(cr.Name, client->name(), sizeof(cr.Name));
366    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
367       db_unlock(ua->db);
368       return 0;
369    }
370
371    if (pool && pool->JobRetention > 0) {
372       period = pool->JobRetention;
373    } else {
374       period = client->JobRetention;
375    }
376    now = (utime_t)time(NULL);
377
378    /* Drop any previous temporary tables still there */
379    drop_temp_tables(ua);
380
381    /* Create temp tables and indicies */
382    if (!create_temp_tables(ua)) {
383       goto bail_out;
384    }
385
386    edit_utime(period, ed1, sizeof(ed1));
387    Jmsg(ua->jcr, M_INFO, 0, _("Begin pruning Jobs older than %s.\n"), ed1);
388
389    edit_int64(now - period, ed1); /* Jobs older than ed1 are good candidates */
390    edit_int64(cr.ClientId, ed2);
391
392    memset(&del, 0, sizeof(del));
393    del.max_ids = 100;
394    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
395    del.PurgedFiles = (char *)malloc(del.max_ids);
396
397    /* Prune garbage jobs (JobStatus not successful) */
398    Mmsg(query, 
399    "SELECT JobId, PurgedFiles FROM Job "
400     "WHERE ( JobFiles=0 "
401          "OR JobStatus NOT IN ('T', 'W') "
402           ") "
403       "AND JobTDate < %s "
404       "AND ClientId = %s ",
405         ed1, ed2);
406    
407    Dmsg1(150, "Query=%s\n", query.c_str());
408    if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
409       ua->error_msg("%s", db_strerror(ua->db));
410    }
411
412    /* Prune Admin, Restore, Copy and Migration jobs */
413    Mmsg(query, 
414    "SELECT JobId, PurgedFiles FROM Job "
415     "WHERE JobType IN ('D', 'R', 'c', 'm') "
416       "AND JobTDate < %s "
417       "AND ClientId = %s ",
418         ed1, ed2);
419    
420    Dmsg1(150, "Query=%s\n", query.c_str());
421    if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
422       ua->error_msg("%s", db_strerror(ua->db));
423    }
424
425    /* Select all backups that can be used */
426    /* need to check by fileset name  */
427    /* need to check unused base jobs */
428    /* how we do with old fileset.... */
429    /* did we check only for defined jobs ? */
430    Mmsg(query, 
431    "SELECT JobId, FileSet, PurgedFiles FROM Job "
432     "WHERE JobType IN = 'B' "
433       "AND JobTDate < %s "
434       "AND ClientId = %s ",
435         ed1, ed2);
436    
437    Dmsg1(150, "Query=%s\n", query.c_str());
438    if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
439       ua->error_msg("%s", db_strerror(ua->db));
440    }
441    
442    /*
443     * Select all files that are older than the JobRetention period
444     *  and stuff them into the "DeletionCandidates" table.
445     */
446    Mmsg(query, insert_delcand, (char)JobType, ed1, 
447         edit_int64(cr.ClientId, ed2));
448    if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) {
449       if (ua->verbose) {
450          ua->error_msg("%s", db_strerror(ua->db));
451       }
452       Dmsg0(050, "insert delcand failed\n");
453       goto bail_out;
454    }
455
456
457    switch (JobType) {
458    case JT_BACKUP:
459       Mmsg(query, select_backup_del, ed1, ed2);
460       break;
461    case JT_RESTORE:
462       Mmsg(query, select_restore_del, ed1, ed2);
463       break;
464    case JT_VERIFY:
465       Mmsg(query, select_verify_del, ed1, ed2);
466       break;
467    case JT_ADMIN:
468       Mmsg(query, select_admin_del, ed1, ed2);
469       break;
470    case JT_COPY:
471       Mmsg(query, select_copy_del, ed1, ed2);
472       break;
473    case JT_MIGRATE:
474       Mmsg(query, select_migrate_del, ed1, ed2);
475       break;
476    }
477
478    Dmsg1(150, "Query=%s\n", query.c_str());
479    if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
480       ua->error_msg("%s", db_strerror(ua->db));
481    }
482
483    purge_job_list_from_catalog(ua, del);
484
485    if (del.num_del > 0) {
486       ua->info_msg(_("Pruned %d %s for client %s from catalog.\n"), del.num_del,
487          del.num_del==1?_("Job"):_("Jobs"), client->name());
488     } else if (ua->verbose) {
489        ua->info_msg(_("No Jobs found to prune.\n"));
490     }
491
492 bail_out:
493    drop_temp_tables(ua);
494    db_unlock(ua->db);
495    if (del.JobId) {
496       free(del.JobId);
497    }
498    if (del.PurgedFiles) {
499       free(del.PurgedFiles);
500    }
501    return 1;
502 }
503
504 /*
505  * Prune a given Volume
506  */
507 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
508 {
509    POOL_MEM query(PM_MESSAGE);
510    struct del_ctx del;
511    bool ok = false;
512    int count;
513
514    if (mr->Enabled == 2) {
515       return false;                   /* Cannot prune archived volumes */
516    }
517
518    memset(&del, 0, sizeof(del));
519    del.max_ids = 10000;
520    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
521
522    db_lock(ua->db);
523
524    /* Prune only Volumes with status "Full", or "Used" */
525    if (strcmp(mr->VolStatus, "Full")   == 0 ||
526        strcmp(mr->VolStatus, "Used")   == 0) {
527       Dmsg2(050, "get prune list MediaId=%d Volume %s\n", (int)mr->MediaId, mr->VolumeName);
528       count = get_prune_list_for_volume(ua, mr, &del);
529       Dmsg1(050, "Num pruned = %d\n", count);
530       if (count != 0) {
531          purge_job_list_from_catalog(ua, del);
532       }
533       ok = is_volume_purged(ua, mr);
534    }
535
536    db_unlock(ua->db);
537    if (del.JobId) {
538       free(del.JobId);
539    }
540    return ok;
541 }
542
543 /*
544  * Get prune list for a volume
545  */
546 int get_prune_list_for_volume(UAContext *ua, MEDIA_DBR *mr, del_ctx *del)
547 {
548    POOL_MEM query(PM_MESSAGE);
549    int count = 0;
550    utime_t now, period;
551    char ed1[50], ed2[50];
552
553    if (mr->Enabled == 2) {
554       return 0;                    /* cannot prune Archived volumes */
555    }
556
557    /*
558     * Now add to the  list of JobIds for Jobs written to this Volume
559     */
560    edit_int64(mr->MediaId, ed1); 
561    period = mr->VolRetention;
562    now = (utime_t)time(NULL);
563    edit_int64(now-period, ed2);
564    Mmsg(query, sel_JobMedia, ed1, ed2);
565    Dmsg3(250, "Now=%d period=%d now-period=%s\n", (int)now, (int)period,
566       ed2);
567
568    Dmsg1(050, "Query=%s\n", query.c_str());
569    if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)del)) {
570       if (ua->verbose) {
571          ua->error_msg("%s", db_strerror(ua->db));
572       }
573       Dmsg0(050, "Count failed\n");
574       goto bail_out;
575    }
576    count = exclude_running_jobs_from_list(del);
577    
578 bail_out:
579    return count;
580 }
581
582 /*
583  * We have a list of jobs to prune or purge. If any of them is
584  *   currently running, we set its JobId to zero which effectively
585  *   excludes it.
586  *
587  * Returns the number of jobs that can be prunned or purged.
588  *
589  */
590 int exclude_running_jobs_from_list(del_ctx *prune_list)
591 {
592    int count = 0;
593    JCR *jcr;
594    bool skip;
595    int i;          
596
597    /* Do not prune any job currently running */
598    for (i=0; i < prune_list->num_ids; i++) {
599       skip = false;
600       foreach_jcr(jcr) {
601          if (jcr->JobId == prune_list->JobId[i]) {
602             Dmsg2(050, "skip running job JobId[%d]=%d\n", i, (int)prune_list->JobId[i]);
603             prune_list->JobId[i] = 0;
604             skip = true;
605             break;
606          }
607       }
608       endeach_jcr(jcr);
609       if (skip) {
610          continue;  /* don't increment count */
611       }
612       Dmsg2(050, "accept JobId[%d]=%d\n", i, (int)prune_list->JobId[i]);
613       count++;
614    }
615    return count;
616 }