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