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