]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_prune.c
Merge branch 'master' into readline-bconsole-restore
[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    /* Select Jobs -- for counting */ 
268    edit_int64(now - period, ed1);
269    Mmsg(query, count_select_job, ed1, edit_int64(cr.ClientId, ed2));
270    Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, 
271                (uint32_t)period, query.c_str());
272    cnt.count = 0;
273    if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
274       ua->error_msg("%s", db_strerror(ua->db));
275       Dmsg0(050, "Count failed\n");
276       goto bail_out;
277    }
278
279    if (cnt.count == 0) {
280       if (ua->verbose) {
281          ua->warning_msg(_("No Files found to prune.\n"));
282       }
283       goto bail_out;
284    }
285
286    if (cnt.count < MAX_DEL_LIST_LEN) {
287       del.max_ids = cnt.count + 1;
288    } else {
289       del.max_ids = MAX_DEL_LIST_LEN;
290    }
291    del.tot_ids = 0;
292
293    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
294
295    /* Now process same set but making a delete list */
296    Mmsg(query, select_job, edit_int64(now - period, ed1), 
297         edit_int64(cr.ClientId, ed2));
298    db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del);
299
300    purge_files_from_job_list(ua, del);
301
302    edit_uint64_with_commas(del.num_del, ed1);
303    ua->info_msg(_("Pruned Files from %s Jobs for client %s from catalog.\n"),
304       ed1, client->name());
305
306 bail_out:
307    db_unlock(ua->db);
308    if (del.JobId) {
309       free(del.JobId);
310    }
311    return 1;
312 }
313
314
315 static void drop_temp_tables(UAContext *ua)
316 {
317    int i;
318    for (i=0; drop_deltabs[i]; i++) {
319       db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
320    }
321 }
322
323 static bool create_temp_tables(UAContext *ua)
324 {
325    /* Create temp tables and indicies */
326    if (!db_sql_query(ua->db, create_deltabs[db_type], NULL, (void *)NULL)) {
327       ua->error_msg("%s", db_strerror(ua->db));
328       Dmsg0(050, "create DelTables table failed\n");
329       return false;
330    }
331    if (!db_sql_query(ua->db, create_delindex, NULL, (void *)NULL)) {
332        ua->error_msg("%s", db_strerror(ua->db));
333        Dmsg0(050, "create DelInx1 index failed\n");
334        return false;
335    }
336    return true;
337 }
338
339
340
341 /*
342  * Pruning Jobs is a bit more complicated than purging Files
343  * because we delete Job records only if there is a more current
344  * backup of the FileSet. Otherwise, we keep the Job record.
345  * In other words, we never delete the only Job record that
346  * contains a current backup of a FileSet. This prevents the
347  * Volume from being recycled and destroying a current backup.
348  *
349  * For Verify Jobs, we do not delete the last InitCatalog.
350  *
351  * For Restore Jobs there are no restrictions.
352  */
353 int prune_jobs(UAContext *ua, CLIENT *client, POOL *pool, int JobType)
354 {
355    struct del_ctx del;
356    POOL_MEM query(PM_MESSAGE);
357    utime_t now, period;
358    CLIENT_DBR cr;
359    char ed1[50], ed2[50];
360
361    db_lock(ua->db);
362    memset(&cr, 0, sizeof(cr));
363    memset(&del, 0, sizeof(del));
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
387    /*
388     * Select all files that are older than the JobRetention period
389     *  and stuff them into the "DeletionCandidates" table.
390     */
391    edit_utime(now-period, ed1, sizeof(ed1));
392    Jmsg(ua->jcr, M_INFO, 0, _("Begin pruning Jobs older than %s secs.\n"), ed1);
393    edit_int64(now - period, ed1);
394    Mmsg(query, insert_delcand, (char)JobType, ed1, 
395         edit_int64(cr.ClientId, ed2));
396    if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) {
397       if (ua->verbose) {
398          ua->error_msg("%s", db_strerror(ua->db));
399       }
400       Dmsg0(050, "insert delcand failed\n");
401       goto bail_out;
402    }
403
404    del.max_ids = 100;
405    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
406    del.PurgedFiles = (char *)malloc(del.max_ids);
407
408    /* ed1 = JobTDate */
409    edit_int64(cr.ClientId, ed2);
410    switch (JobType) {
411    case JT_BACKUP:
412       Mmsg(query, select_backup_del, ed1, ed2);
413       break;
414    case JT_RESTORE:
415       Mmsg(query, select_restore_del, ed1, ed2);
416       break;
417    case JT_VERIFY:
418       Mmsg(query, select_verify_del, ed1, ed2);
419       break;
420    case JT_ADMIN:
421       Mmsg(query, select_admin_del, ed1, ed2);
422       break;
423    case JT_COPY:
424       Mmsg(query, select_copy_del, ed1, ed2);
425       break;
426    case JT_MIGRATE:
427       Mmsg(query, select_migrate_del, ed1, ed2);
428       break;
429    }
430
431    Dmsg1(150, "Query=%s\n", query.c_str());
432    if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
433       ua->error_msg("%s", db_strerror(ua->db));
434    }
435
436    purge_job_list_from_catalog(ua, del);
437
438    if (del.num_del > 0) {
439       ua->info_msg(_("Pruned %d %s for client %s from catalog.\n"), del.num_del,
440          del.num_del==1?_("Job"):_("Jobs"), client->name());
441     } else if (ua->verbose) {
442        ua->info_msg(_("No Jobs found to prune.\n"));
443     }
444
445 bail_out:
446    drop_temp_tables(ua);
447    db_unlock(ua->db);
448    if (del.JobId) {
449       free(del.JobId);
450    }
451    if (del.PurgedFiles) {
452       free(del.PurgedFiles);
453    }
454    return 1;
455 }
456
457 /*
458  * Prune a given Volume
459  */
460 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
461 {
462    POOL_MEM query(PM_MESSAGE);
463    struct del_ctx del;
464    bool ok = false;
465    int count;
466
467    if (mr->Enabled == 2) {
468       return false;                   /* Cannot prune archived volumes */
469    }
470
471    memset(&del, 0, sizeof(del));
472    del.max_ids = 10000;
473    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
474
475    db_lock(ua->db);
476
477    /* Prune only Volumes with status "Full", or "Used" */
478    if (strcmp(mr->VolStatus, "Full")   == 0 ||
479        strcmp(mr->VolStatus, "Used")   == 0) {
480       Dmsg2(050, "get prune list MediaId=%d Volume %s\n", (int)mr->MediaId, mr->VolumeName);
481       count = get_prune_list_for_volume(ua, mr, &del);
482       Dmsg1(050, "Num pruned = %d\n", count);
483       if (count != 0) {
484          purge_job_list_from_catalog(ua, del);
485       }
486       ok = is_volume_purged(ua, mr);
487    }
488
489    db_unlock(ua->db);
490    if (del.JobId) {
491       free(del.JobId);
492    }
493    return ok;
494 }
495
496 /*
497  * Get prune list for a volume
498  */
499 int get_prune_list_for_volume(UAContext *ua, MEDIA_DBR *mr, del_ctx *del)
500 {
501    POOL_MEM query(PM_MESSAGE);
502    int count = 0;
503    utime_t now, period;
504    char ed1[50], ed2[50];
505
506    if (mr->Enabled == 2) {
507       return 0;                    /* cannot prune Archived volumes */
508    }
509
510    /*
511     * Now add to the  list of JobIds for Jobs written to this Volume
512     */
513    edit_int64(mr->MediaId, ed1); 
514    period = mr->VolRetention;
515    now = (utime_t)time(NULL);
516    edit_int64(now-period, ed2);
517    Mmsg(query, sel_JobMedia, ed1, ed2);
518    Dmsg3(250, "Now=%d period=%d now-period=%s\n", (int)now, (int)period,
519       ed2);
520
521    Dmsg1(050, "Query=%s\n", query.c_str());
522    if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)del)) {
523       if (ua->verbose) {
524          ua->error_msg("%s", db_strerror(ua->db));
525       }
526       Dmsg0(050, "Count failed\n");
527       goto bail_out;
528    }
529    count = exclude_running_jobs_from_list(del);
530    
531 bail_out:
532    return count;
533 }
534
535 /*
536  * We have a list of jobs to prune or purge. If any of them is
537  *   currently running, we set its JobId to zero which effectively
538  *   excludes it.
539  *
540  * Returns the number of jobs that can be prunned or purged.
541  *
542  */
543 int exclude_running_jobs_from_list(del_ctx *prune_list)
544 {
545    int count = 0;
546    JCR *jcr;
547    bool skip;
548    int i;          
549
550    /* Do not prune any job currently running */
551    for (i=0; i < prune_list->num_ids; i++) {
552       skip = false;
553       foreach_jcr(jcr) {
554          if (jcr->JobId == prune_list->JobId[i]) {
555             Dmsg2(050, "skip running job JobId[%d]=%d\n", i, (int)prune_list->JobId[i]);
556             prune_list->JobId[i] = 0;
557             skip = true;
558             break;
559          }
560       }
561       endeach_jcr(jcr);
562       if (skip) {
563          continue;  /* don't increment count */
564       }
565       Dmsg2(050, "accept JobId[%d]=%d\n", i, (int)prune_list->JobId[i]);
566       count++;
567    }
568    return count;
569 }