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