]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_prune.c
Remove double secs in pruning output
[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    memset(&del, 0, sizeof(del));
365
366    bstrncpy(cr.Name, client->name(), sizeof(cr.Name));
367    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
368       db_unlock(ua->db);
369       return 0;
370    }
371
372    if (pool && pool->JobRetention > 0) {
373       period = pool->JobRetention;
374    } else {
375       period = client->JobRetention;
376    }
377    now = (utime_t)time(NULL);
378
379    /* Drop any previous temporary tables still there */
380    drop_temp_tables(ua);
381
382    /* Create temp tables and indicies */
383    if (!create_temp_tables(ua)) {
384       goto bail_out;
385    }
386
387
388    /*
389     * Select all files that are older than the JobRetention period
390     *  and stuff them into the "DeletionCandidates" table.
391     */
392    edit_utime(now-period, ed1, sizeof(ed1));
393    Jmsg(ua->jcr, M_INFO, 0, _("Begin pruning Jobs older than %s.\n"), ed1);
394    edit_int64(now - period, ed1);
395    Mmsg(query, insert_delcand, (char)JobType, ed1, 
396         edit_int64(cr.ClientId, ed2));
397    if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) {
398       if (ua->verbose) {
399          ua->error_msg("%s", db_strerror(ua->db));
400       }
401       Dmsg0(050, "insert delcand failed\n");
402       goto bail_out;
403    }
404
405    del.max_ids = 100;
406    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
407    del.PurgedFiles = (char *)malloc(del.max_ids);
408
409    /* ed1 = JobTDate */
410    edit_int64(cr.ClientId, ed2);
411    switch (JobType) {
412    case JT_BACKUP:
413       Mmsg(query, select_backup_del, ed1, ed2);
414       break;
415    case JT_RESTORE:
416       Mmsg(query, select_restore_del, ed1, ed2);
417       break;
418    case JT_VERIFY:
419       Mmsg(query, select_verify_del, ed1, ed2);
420       break;
421    case JT_ADMIN:
422       Mmsg(query, select_admin_del, ed1, ed2);
423       break;
424    case JT_COPY:
425       Mmsg(query, select_copy_del, ed1, ed2);
426       break;
427    case JT_MIGRATE:
428       Mmsg(query, select_migrate_del, ed1, ed2);
429       break;
430    }
431
432    Dmsg1(150, "Query=%s\n", query.c_str());
433    if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
434       ua->error_msg("%s", db_strerror(ua->db));
435    }
436
437    purge_job_list_from_catalog(ua, del);
438
439    if (del.num_del > 0) {
440       ua->info_msg(_("Pruned %d %s for client %s from catalog.\n"), del.num_del,
441          del.num_del==1?_("Job"):_("Jobs"), client->name());
442     } else if (ua->verbose) {
443        ua->info_msg(_("No Jobs found to prune.\n"));
444     }
445
446 bail_out:
447    drop_temp_tables(ua);
448    db_unlock(ua->db);
449    if (del.JobId) {
450       free(del.JobId);
451    }
452    if (del.PurgedFiles) {
453       free(del.PurgedFiles);
454    }
455    return 1;
456 }
457
458 /*
459  * Prune a given Volume
460  */
461 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
462 {
463    POOL_MEM query(PM_MESSAGE);
464    struct del_ctx del;
465    bool ok = false;
466    int count;
467
468    if (mr->Enabled == 2) {
469       return false;                   /* Cannot prune archived volumes */
470    }
471
472    memset(&del, 0, sizeof(del));
473    del.max_ids = 10000;
474    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
475
476    db_lock(ua->db);
477
478    /* Prune only Volumes with status "Full", or "Used" */
479    if (strcmp(mr->VolStatus, "Full")   == 0 ||
480        strcmp(mr->VolStatus, "Used")   == 0) {
481       Dmsg2(050, "get prune list MediaId=%d Volume %s\n", (int)mr->MediaId, mr->VolumeName);
482       count = get_prune_list_for_volume(ua, mr, &del);
483       Dmsg1(050, "Num pruned = %d\n", count);
484       if (count != 0) {
485          purge_job_list_from_catalog(ua, del);
486       }
487       ok = is_volume_purged(ua, mr);
488    }
489
490    db_unlock(ua->db);
491    if (del.JobId) {
492       free(del.JobId);
493    }
494    return ok;
495 }
496
497 /*
498  * Get prune list for a volume
499  */
500 int get_prune_list_for_volume(UAContext *ua, MEDIA_DBR *mr, del_ctx *del)
501 {
502    POOL_MEM query(PM_MESSAGE);
503    int count = 0;
504    utime_t now, period;
505    char ed1[50], ed2[50];
506
507    if (mr->Enabled == 2) {
508       return 0;                    /* cannot prune Archived volumes */
509    }
510
511    /*
512     * Now add to the  list of JobIds for Jobs written to this Volume
513     */
514    edit_int64(mr->MediaId, ed1); 
515    period = mr->VolRetention;
516    now = (utime_t)time(NULL);
517    edit_int64(now-period, ed2);
518    Mmsg(query, sel_JobMedia, ed1, ed2);
519    Dmsg3(250, "Now=%d period=%d now-period=%s\n", (int)now, (int)period,
520       ed2);
521
522    Dmsg1(050, "Query=%s\n", query.c_str());
523    if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)del)) {
524       if (ua->verbose) {
525          ua->error_msg("%s", db_strerror(ua->db));
526       }
527       Dmsg0(050, "Count failed\n");
528       goto bail_out;
529    }
530    count = exclude_running_jobs_from_list(del);
531    
532 bail_out:
533    return count;
534 }
535
536 /*
537  * We have a list of jobs to prune or purge. If any of them is
538  *   currently running, we set its JobId to zero which effectively
539  *   excludes it.
540  *
541  * Returns the number of jobs that can be prunned or purged.
542  *
543  */
544 int exclude_running_jobs_from_list(del_ctx *prune_list)
545 {
546    int count = 0;
547    JCR *jcr;
548    bool skip;
549    int i;          
550
551    /* Do not prune any job currently running */
552    for (i=0; i < prune_list->num_ids; i++) {
553       skip = false;
554       foreach_jcr(jcr) {
555          if (jcr->JobId == prune_list->JobId[i]) {
556             Dmsg2(050, "skip running job JobId[%d]=%d\n", i, (int)prune_list->JobId[i]);
557             prune_list->JobId[i] = 0;
558             skip = true;
559             break;
560          }
561       }
562       endeach_jcr(jcr);
563       if (skip) {
564          continue;  /* don't increment count */
565       }
566       Dmsg2(050, "accept JobId[%d]=%d\n", i, (int)prune_list->JobId[i]);
567       count++;
568    }
569    return count;
570 }