]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_prune.c
kes Move the checking of the database in initializion of the Director
[bacula/bacula] / bacula / src / dird / ua_prune.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2002-2007 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 plus additions
11    that are listed 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 John Walker.
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  *   Version $Id$
36  */
37
38 #include "bacula.h"
39 #include "dird.h"
40
41 /* Imported functions */
42
43 /* Forward referenced functions */
44
45
46 #define MAX_DEL_LIST_LEN 2000000
47
48 /* In memory list of JobIds */
49 struct s_file_del_ctx {
50    JobId_t *JobId;
51    int num_ids;                       /* ids stored */
52    int max_ids;                       /* size of array */
53    int num_del;                       /* number deleted */
54    int tot_ids;                       /* total to process */
55 };
56
57 struct s_job_del_ctx {
58    JobId_t *JobId;                    /* array of JobIds */
59    char *PurgedFiles;                 /* Array of PurgedFile flags */
60    int num_ids;                       /* ids stored */
61    int max_ids;                       /* size of array */
62    int num_del;                       /* number deleted */
63    int tot_ids;                       /* total to process */
64 };
65
66 struct s_count_ctx {
67    int count;
68 };
69
70
71 /*
72  * Called here to count entries to be deleted
73  */
74 static int count_handler(void *ctx, int num_fields, char **row)
75 {
76    struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
77
78    if (row[0]) {
79       cnt->count = str_to_int64(row[0]);
80    } else {
81       cnt->count = 0;
82    }
83    return 0;
84 }
85
86
87 /*
88  * Called here to count the number of Jobs to be pruned
89  */
90 static int file_count_handler(void *ctx, int num_fields, char **row)
91 {
92    struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
93    del->tot_ids++;
94    return 0;
95 }
96
97
98 /*
99  * Called here to make in memory list of JobIds to be
100  *  deleted and the associated PurgedFiles flag.
101  *  The in memory list will then be transversed
102  *  to issue the SQL DELETE commands.  Note, the list
103  *  is allowed to get to MAX_DEL_LIST_LEN to limit the
104  *  maximum malloc'ed memory.
105  */
106 static int job_delete_handler(void *ctx, int num_fields, char **row)
107 {
108    struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
109
110    if (del->num_ids == MAX_DEL_LIST_LEN) {
111       return 1;
112    }
113    if (del->num_ids == del->max_ids) {
114       del->max_ids = (del->max_ids * 3) / 2;
115       del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
116       del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
117    }
118    del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
119    del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[1]);
120    return 0;
121 }
122
123 static int file_delete_handler(void *ctx, int num_fields, char **row)
124 {
125    struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
126
127    if (del->num_ids == MAX_DEL_LIST_LEN) {
128       return 1;
129    }
130    if (del->num_ids == del->max_ids) {
131       del->max_ids = (del->max_ids * 3) / 2;
132       del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
133          del->max_ids);
134    }
135    del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
136    return 0;
137 }
138
139 /*
140  *   Prune records from database
141  *
142  *    prune files (from) client=xxx
143  *    prune jobs (from) client=xxx
144  *    prune volume=xxx
145  */
146 int prunecmd(UAContext *ua, const char *cmd)
147 {
148    CLIENT *client;
149    POOL_DBR pr;
150    MEDIA_DBR mr;
151    int kw;
152
153    static const char *keywords[] = {
154       NT_("Files"),
155       NT_("Jobs"),
156       NT_("Volume"),
157       NULL};
158
159    if (!open_client_db(ua)) {
160       return false;
161    }
162
163    /* First search args */
164    kw = find_arg_keyword(ua, keywords);
165    if (kw < 0 || kw > 2) {
166       /* no args, so ask user */
167       kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);
168    }
169
170    switch (kw) {
171    case 0:  /* prune files */
172       client = get_client_resource(ua);
173       if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
174          return false;
175       }
176       prune_files(ua, client);
177       return true;
178    case 1:  /* prune jobs */
179       client = get_client_resource(ua);
180       if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
181          return false;
182       }
183       /* ****FIXME**** allow user to select JobType */
184       prune_jobs(ua, client, JT_BACKUP);
185       return 1;
186    case 2:  /* prune volume */
187       if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
188          return false;
189       }
190       if (mr.Enabled == 2) {
191          bsendmsg(ua, _("Cannot prune Volume \"%s\" because it is archived.\n"),
192             mr.VolumeName);
193       }
194       if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
195          return false;
196       }
197       prune_volume(ua, &mr);
198       return true;
199    default:
200       break;
201    }
202
203    return true;
204 }
205
206 /*
207  * Prune File records from the database. For any Job which
208  * is older than the retention period, we unconditionally delete
209  * all File records for that Job.  This is simple enough that no
210  * temporary tables are needed. We simply make an in memory list of
211  * the JobIds meeting the prune conditions, then delete all File records
212  * pointing to each of those JobIds.
213  *
214  * This routine assumes you want the pruning to be done. All checking
215  *  must be done before calling this routine.
216  */
217 int prune_files(UAContext *ua, CLIENT *client)
218 {
219    struct s_file_del_ctx del;
220    POOLMEM *query = get_pool_memory(PM_MESSAGE);
221    int i;
222    utime_t now, period;
223    CLIENT_DBR cr;
224    char ed1[50], ed2[50];
225
226    db_lock(ua->db);
227    memset(&cr, 0, sizeof(cr));
228    memset(&del, 0, sizeof(del));
229    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
230    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
231       db_unlock(ua->db);
232       return 0;
233    }
234
235    period = client->FileRetention;
236    now = (utime_t)time(NULL);
237
238    /* Select Jobs -- for counting */
239    Mmsg(query, select_job, edit_uint64(now - period, ed1), 
240         edit_int64(cr.ClientId, ed2));
241    Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, (uint32_t)period, query);
242    if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
243       if (ua->verbose) {
244          bsendmsg(ua, "%s", db_strerror(ua->db));
245       }
246       Dmsg0(050, "Count failed\n");
247       goto bail_out;
248    }
249
250    if (del.tot_ids == 0) {
251       if (ua->verbose) {
252          bsendmsg(ua, _("No Files found to prune.\n"));
253       }
254       goto bail_out;
255    }
256
257    if (del.tot_ids < MAX_DEL_LIST_LEN) {
258       del.max_ids = del.tot_ids + 1;
259    } else {
260       del.max_ids = MAX_DEL_LIST_LEN;
261    }
262    del.tot_ids = 0;
263
264    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
265
266    /* Now process same set but making a delete list */
267    db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
268
269    for (i=0; i < del.num_ids; i++) {
270       /* Don't prune current job */
271       if (ua->jcr->JobId != del.JobId[i]) {
272          purge_files_from_job(ua, del.JobId[i]);
273          del.num_del++;
274       }
275    }
276    edit_uint64_with_commas(del.num_del, ed1);
277    bsendmsg(ua, _("Pruned Files from %s Jobs for client %s from catalog.\n"),
278       ed1, client->name());
279
280 bail_out:
281    db_unlock(ua->db);
282    if (del.JobId) {
283       free(del.JobId);
284    }
285    free_pool_memory(query);
286    return 1;
287 }
288
289
290 static void drop_temp_tables(UAContext *ua)
291 {
292    int i;
293    for (i=0; drop_deltabs[i]; i++) {
294       db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
295    }
296 }
297
298 static bool create_temp_tables(UAContext *ua)
299 {
300    int i;
301    /* Create temp tables and indicies */
302    for (i=0; create_deltabs[i]; i++) {
303       if (!db_sql_query(ua->db, create_deltabs[i], NULL, (void *)NULL)) {
304          bsendmsg(ua, "%s", db_strerror(ua->db));
305          Dmsg0(050, "create DelTables table failed\n");
306          return false;
307       }
308    }
309    return true;
310 }
311
312
313
314 /*
315  * Pruning Jobs is a bit more complicated than purging Files
316  * because we delete Job records only if there is a more current
317  * backup of the FileSet. Otherwise, we keep the Job record.
318  * In other words, we never delete the only Job record that
319  * contains a current backup of a FileSet. This prevents the
320  * Volume from being recycled and destroying a current backup.
321  *
322  * For Verify Jobs, we do not delete the last InitCatalog.
323  *
324  * For Restore Jobs there are no restrictions.
325  */
326 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
327 {
328    struct s_job_del_ctx del;
329    struct s_count_ctx cnt;
330    POOLMEM *query = get_pool_memory(PM_MESSAGE);
331    int i;
332    utime_t now, period;
333    CLIENT_DBR cr;
334    char ed1[50], ed2[50];
335
336    db_lock(ua->db);
337    memset(&cr, 0, sizeof(cr));
338    memset(&del, 0, sizeof(del));
339    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
340    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
341       db_unlock(ua->db);
342       return 0;
343    }
344
345    period = client->JobRetention;
346    now = (utime_t)time(NULL);
347
348    /* Drop any previous temporary tables still there */
349    drop_temp_tables(ua);
350
351    /* Create temp tables and indicies */
352    if (!create_temp_tables(ua)) {
353       goto bail_out;
354    }
355
356    /*
357     * Select all files that are older than the JobRetention period
358     *  and stuff them into the "DeletionCandidates" table.
359     */
360    edit_uint64(now - period, ed1);
361    Mmsg(query, insert_delcand, (char)JobType, ed1, 
362         edit_int64(cr.ClientId, ed2));
363    if (!db_sql_query(ua->db, query, NULL, (void *)NULL)) {
364       if (ua->verbose) {
365          bsendmsg(ua, "%s", db_strerror(ua->db));
366       }
367       Dmsg0(050, "insert delcand failed\n");
368       goto bail_out;
369    }
370
371    /* Count Files to be deleted */
372    pm_strcpy(query, cnt_DelCand);
373    Dmsg1(100, "select sql=%s\n", query);
374    cnt.count = 0;
375    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
376       bsendmsg(ua, "%s", db_strerror(ua->db));
377       Dmsg0(050, "Count failed\n");
378       goto bail_out;
379    }
380
381    if (cnt.count == 0) {
382       if (ua->verbose) {
383          bsendmsg(ua, _("No Jobs found to prune.\n"));
384       }
385       goto bail_out;
386    }
387
388    if (cnt.count < MAX_DEL_LIST_LEN) {
389       del.max_ids = cnt.count + 1;
390    } else {
391       del.max_ids = MAX_DEL_LIST_LEN;
392    }
393    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
394    del.PurgedFiles = (char *)malloc(del.max_ids);
395
396    /* ed1 = JobTDate */
397    edit_int64(cr.ClientId, ed2);
398    switch (JobType) {
399    case JT_BACKUP:
400       Mmsg(query, select_backup_del, ed1, ed2);
401       break;
402    case JT_RESTORE:
403       Mmsg(query, select_restore_del, ed1, ed2);
404       break;
405    case JT_VERIFY:
406       Mmsg(query, select_verify_del, ed1, ed2);
407       break;
408    case JT_ADMIN:
409       Mmsg(query, select_admin_del, ed1, ed2);
410       break;
411    case JT_MIGRATE:
412       Mmsg(query, select_migrate_del, ed1, ed2);
413       break;
414    }
415    if (!db_sql_query(ua->db, query, job_delete_handler, (void *)&del)) {
416       bsendmsg(ua, "%s", db_strerror(ua->db));
417    }
418
419    /*
420     * OK, now we have the list of JobId's to be pruned, first check
421     * if the Files have been purged, if not, purge (delete) them.
422     * Then delete the Job entry, and finally and JobMedia records.
423     */
424    for (i=0; i < del.num_ids; i++) {
425       /* Don't prune current job */
426       if (ua->jcr->JobId != del.JobId[i]) {
427          if (!del.PurgedFiles[i]) {
428             purge_files_from_job(ua, del.JobId[i]);
429          }
430          purge_job_from_catalog(ua, del.JobId[i]);
431          del.num_del++;
432       }
433    }
434    bsendmsg(ua, _("Pruned %d %s for client %s from catalog.\n"), del.num_del,
435       del.num_del==1?_("Job"):_("Jobs"), client->name());
436
437 bail_out:
438    drop_temp_tables(ua);
439    db_unlock(ua->db);
440    if (del.JobId) {
441       free(del.JobId);
442    }
443    if (del.PurgedFiles) {
444       free(del.PurgedFiles);
445    }
446    free_pool_memory(query);
447    return 1;
448 }
449
450 /*
451  * Prune a given Volume
452  */
453 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
454 {
455    POOLMEM *query = get_pool_memory(PM_MESSAGE);
456    struct s_count_ctx cnt;
457    struct s_file_del_ctx del;
458    int i;          
459    bool ok = false;
460    JOB_DBR jr;
461    utime_t now, period;
462    char ed1[50];
463
464    if (mr->Enabled == 2) {
465       return false;                   /* Cannot prune archived volumes */
466    }
467
468    db_lock(ua->db);
469    memset(&jr, 0, sizeof(jr));
470    memset(&del, 0, sizeof(del));
471
472    /*
473     * Find out how many Jobs remain on this Volume by
474     *  counting the JobMedia records.
475     */
476    cnt.count = 0;
477    Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
478    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
479       bsendmsg(ua, "%s", db_strerror(ua->db));
480       Dmsg0(050, "Count failed\n");
481       goto bail_out;
482    }
483
484    if (cnt.count == 0) {
485       /* Don't mark appendable volume as purged */
486       if (strcmp(mr->VolStatus, "Append") == 0 && verbose) {
487          bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Prune not needed.\n"),
488             mr->VolumeName);
489          ok = true;
490          goto bail_out;
491       }
492       /* If volume not already purged, do so */
493       if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
494          bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
495             mr->VolumeName);
496       }
497       ok = mark_media_purged(ua, mr);
498       goto bail_out;
499    }
500
501    if (cnt.count < MAX_DEL_LIST_LEN) {
502       del.max_ids = cnt.count + 1;
503    } else {
504       del.max_ids = MAX_DEL_LIST_LEN;
505    }
506
507    /*
508     * Now get a list of JobIds for Jobs written to this Volume
509     *   Could optimize here by adding JobTDate > (now - period).
510     */
511    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
512    Mmsg(query, sel_JobMedia, edit_int64(mr->MediaId, ed1));
513    if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
514       if (ua->verbose) {
515          bsendmsg(ua, "%s", db_strerror(ua->db));
516       }
517       Dmsg0(050, "Count failed\n");
518       goto bail_out;
519    }
520
521    /* Use Volume Retention to prune Jobs and their Files */
522    period = mr->VolRetention;
523    now = (utime_t)time(NULL);
524
525    Dmsg3(200, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
526       (int)(now-period));
527
528    for (i=0; i < del.num_ids; i++) {
529       jr.JobId = del.JobId[i];
530       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
531          continue;
532       }
533       Dmsg2(200, "Looking at %s JobTdate=%d\n", jr.Job, (int)jr.JobTDate);
534       if (jr.JobTDate >= (now - period) || ua->jcr->JobId == del.JobId[i]) {
535          continue;
536       }
537       purge_files_from_job(ua, del.JobId[i]);
538       purge_job_from_catalog(ua, del.JobId[i]);
539       del.num_del++;
540    }
541    if (del.JobId) {
542       free(del.JobId);
543    }
544    if (ua->verbose && del.num_del != 0) {
545       bsendmsg(ua, _("Pruned %d %s on Volume \"%s\" from catalog.\n"), del.num_del,
546          del.num_del == 1 ? "Job" : "Jobs", mr->VolumeName);
547    }
548
549    /*
550     * Find out how many Jobs remain on this Volume by
551     *  counting the JobMedia records.
552     */
553    cnt.count = 0;
554    Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
555    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
556       bsendmsg(ua, "%s", db_strerror(ua->db));
557       Dmsg0(050, "Count failed\n");
558       goto bail_out;
559    }
560    if (cnt.count == 0) {
561       Dmsg0(200, "Volume is purged.\n");
562       ok = mark_media_purged(ua, mr);
563    }
564
565 bail_out:
566    db_unlock(ua->db);
567    free_pool_memory(query);
568    return ok;
569 }