]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_prune.c
ebl Rename JobStat to JobHistory
[bacula/bacula] / bacula / src / dird / ua_prune.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2002-2008 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  *   Version $Id$
36  */
37
38 #include "bacula.h"
39 #include "dird.h"
40
41 /* Imported functions */
42
43 /* Forward referenced functions */
44
45 /*
46  * Called here to count entries to be deleted
47  */
48 int del_count_handler(void *ctx, int num_fields, char **row)
49 {
50    struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
51
52    if (row[0]) {
53       cnt->count = str_to_int64(row[0]);
54    } else {
55       cnt->count = 0;
56    }
57    return 0;
58 }
59
60
61 /*
62  * Called here to make in memory list of JobIds to be
63  *  deleted and the associated PurgedFiles flag.
64  *  The in memory list will then be transversed
65  *  to issue the SQL DELETE commands.  Note, the list
66  *  is allowed to get to MAX_DEL_LIST_LEN to limit the
67  *  maximum malloc'ed memory.
68  */
69 int job_delete_handler(void *ctx, int num_fields, char **row)
70 {
71    struct del_ctx *del = (struct del_ctx *)ctx;
72
73    if (del->num_ids == MAX_DEL_LIST_LEN) {
74       return 1;
75    }
76    if (del->num_ids == del->max_ids) {
77       del->max_ids = (del->max_ids * 3) / 2;
78       del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
79       del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
80    }
81    del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
82 // Dmsg2(60, "row=%d val=%d\n", del->num_ids, del->JobId[del->num_ids]);
83    del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[1]);
84    return 0;
85 }
86
87 int file_delete_handler(void *ctx, int num_fields, char **row)
88 {
89    struct del_ctx *del = (struct del_ctx *)ctx;
90
91    if (del->num_ids == MAX_DEL_LIST_LEN) {
92       return 1;
93    }
94    if (del->num_ids == del->max_ids) {
95       del->max_ids = (del->max_ids * 3) / 2;
96       del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
97          del->max_ids);
98    }
99    del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
100 // Dmsg2(150, "row=%d val=%d\n", del->num_ids-1, del->JobId[del->num_ids-1]);
101    return 0;
102 }
103
104 /*
105  *   Prune records from database
106  *
107  *    prune files (from) client=xxx
108  *    prune jobs (from) client=xxx
109  *    prune volume=xxx
110  *    prune stats
111  */
112 int prunecmd(UAContext *ua, const char *cmd)
113 {
114    DIRRES *dir;
115    CLIENT *client;
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 (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
143          return false;
144       }
145       prune_files(ua, client);
146       return true;
147    case 1:  /* prune jobs */
148       client = get_client_resource(ua);
149       if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
150          return false;
151       }
152       /* ****FIXME**** allow user to select JobType */
153       prune_jobs(ua, client, JT_BACKUP);
154       return 1;
155    case 2:  /* prune volume */
156       if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
157          return false;
158       }
159       if (mr.Enabled == 2) {
160          ua->error_msg(_("Cannot prune Volume \"%s\" because it is archived.\n"),
161             mr.VolumeName);
162          return false;
163       }
164       if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
165          return false;
166       }
167       prune_volume(ua, &mr);
168       return true;
169    case 3:  /* prune stats */
170       dir = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
171       if (!dir->stats_retention) {
172          return false;
173       }
174       retention = dir->stats_retention;
175       if (!confirm_retention(ua, &retention, "Statistics")) {
176          return false;
177       }
178       prune_stats(ua, retention);
179       return true;
180    default:
181       break;
182    }
183
184    return true;
185 }
186
187 /* Prune Job stat records from the database. 
188  *
189  */
190 int prune_stats(UAContext *ua, utime_t retention)
191 {
192    char ed1[50];
193    POOL_MEM query(PM_MESSAGE);
194    utime_t now = (utime_t)time(NULL);
195
196    db_lock(ua->db);
197    Mmsg(query, "DELETE FROM JobHistory WHERE JobTDate < %s", 
198         edit_uint64(now - retention, ed1));
199    db_sql_query(ua->db, query.c_str(), NULL, NULL);
200    db_unlock(ua->db);
201
202    ua->info_msg(_("Pruned Jobs from JobHistory catalog.\n"));
203
204    return true;
205 }
206
207 /*
208  * Prune File records from the database. For any Job which
209  * is older than the retention period, we unconditionally delete
210  * all File records for that Job.  This is simple enough that no
211  * temporary tables are needed. We simply make an in memory list of
212  * the JobIds meeting the prune conditions, then delete all File records
213  * pointing to each of those JobIds.
214  *
215  * This routine assumes you want the pruning to be done. All checking
216  *  must be done before calling this routine.
217  */
218 int prune_files(UAContext *ua, CLIENT *client)
219 {
220    struct del_ctx del;
221    struct s_count_ctx cnt;
222    POOL_MEM query(PM_MESSAGE);
223    utime_t now, period;
224    CLIENT_DBR cr;
225    char ed1[50], ed2[50];
226
227    db_lock(ua->db);
228    memset(&cr, 0, sizeof(cr));
229    memset(&del, 0, sizeof(del));
230    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
231    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
232       db_unlock(ua->db);
233       return 0;
234    }
235
236    period = client->FileRetention;
237    now = (utime_t)time(NULL);
238
239    /* Select Jobs -- for counting */
240    Mmsg(query, count_select_job, edit_uint64(now - period, ed1), 
241         edit_int64(cr.ClientId, ed2));
242    Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, 
243                (uint32_t)period, query.c_str());
244    cnt.count = 0;
245    if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
246       ua->error_msg("%s", db_strerror(ua->db));
247       Dmsg0(050, "Count failed\n");
248       goto bail_out;
249    }
250
251    if (cnt.count == 0) {
252       if (ua->verbose) {
253          ua->warning_msg(_("No Files found to prune.\n"));
254       }
255       goto bail_out;
256    }
257
258    if (cnt.count < MAX_DEL_LIST_LEN) {
259       del.max_ids = cnt.count + 1;
260    } else {
261       del.max_ids = MAX_DEL_LIST_LEN;
262    }
263    del.tot_ids = 0;
264
265    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
266
267    /* Now process same set but making a delete list */
268    Mmsg(query, select_job, edit_uint64(now - period, ed1), 
269         edit_int64(cr.ClientId, ed2));
270    db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del);
271
272    purge_files_from_job_list(ua, del);
273
274    edit_uint64_with_commas(del.num_del, ed1);
275    ua->info_msg(_("Pruned Files from %s Jobs for client %s from catalog.\n"),
276       ed1, client->name());
277
278 bail_out:
279    db_unlock(ua->db);
280    if (del.JobId) {
281       free(del.JobId);
282    }
283    return 1;
284 }
285
286
287 static void drop_temp_tables(UAContext *ua)
288 {
289    int i;
290    for (i=0; drop_deltabs[i]; i++) {
291       db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
292    }
293 }
294
295 static bool create_temp_tables(UAContext *ua)
296 {
297    /* Create temp tables and indicies */
298    if (!db_sql_query(ua->db, create_deltabs[db_type], NULL, (void *)NULL)) {
299       ua->error_msg("%s", db_strerror(ua->db));
300       Dmsg0(050, "create DelTables table failed\n");
301       return false;
302    }
303    if (!db_sql_query(ua->db, create_delindex, NULL, (void *)NULL)) {
304        ua->error_msg("%s", db_strerror(ua->db));
305        Dmsg0(050, "create DelInx1 index failed\n");
306        return false;
307    }
308    return true;
309 }
310
311
312
313 /*
314  * Pruning Jobs is a bit more complicated than purging Files
315  * because we delete Job records only if there is a more current
316  * backup of the FileSet. Otherwise, we keep the Job record.
317  * In other words, we never delete the only Job record that
318  * contains a current backup of a FileSet. This prevents the
319  * Volume from being recycled and destroying a current backup.
320  *
321  * For Verify Jobs, we do not delete the last InitCatalog.
322  *
323  * For Restore Jobs there are no restrictions.
324  */
325 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
326 {
327    struct del_ctx del;
328    POOL_MEM query(PM_MESSAGE);
329    utime_t now, period;
330    CLIENT_DBR cr;
331    char ed1[50], ed2[50];
332
333    db_lock(ua->db);
334    memset(&cr, 0, sizeof(cr));
335    memset(&del, 0, sizeof(del));
336
337    bstrncpy(cr.Name, client->name(), sizeof(cr.Name));
338    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
339       db_unlock(ua->db);
340       return 0;
341    }
342
343    period = client->JobRetention;
344    now = (utime_t)time(NULL);
345
346    /* Drop any previous temporary tables still there */
347    drop_temp_tables(ua);
348
349    /* Create temp tables and indicies */
350    if (!create_temp_tables(ua)) {
351       goto bail_out;
352    }
353
354    /*
355     * Select all files that are older than the JobRetention period
356     *  and stuff them into the "DeletionCandidates" table.
357     */
358    edit_uint64(now - period, ed1);
359    Mmsg(query, insert_delcand, (char)JobType, ed1, 
360         edit_int64(cr.ClientId, ed2));
361    if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) {
362       if (ua->verbose) {
363          ua->error_msg("%s", db_strerror(ua->db));
364       }
365       Dmsg0(050, "insert delcand failed\n");
366       goto bail_out;
367    }
368
369    del.max_ids = 100;
370    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
371    del.PurgedFiles = (char *)malloc(del.max_ids);
372
373    /* ed1 = JobTDate */
374    edit_int64(cr.ClientId, ed2);
375    switch (JobType) {
376    case JT_BACKUP:
377       Mmsg(query, select_backup_del, ed1, ed2);
378       break;
379    case JT_RESTORE:
380       Mmsg(query, select_restore_del, ed1, ed2);
381       break;
382    case JT_VERIFY:
383       Mmsg(query, select_verify_del, ed1, ed2);
384       break;
385    case JT_ADMIN:
386       Mmsg(query, select_admin_del, ed1, ed2);
387       break;
388    case JT_COPY:
389       Mmsg(query, select_copy_del, ed1, ed2);
390       break;
391    case JT_MIGRATE:
392       Mmsg(query, select_migrate_del, ed1, ed2);
393       break;
394    }
395
396    Dmsg1(150, "Query=%s\n", query.c_str());
397    if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
398       ua->error_msg("%s", db_strerror(ua->db));
399    }
400
401    purge_job_list_from_catalog(ua, del);
402
403    if (del.num_del > 0) {
404       ua->info_msg(_("Pruned %d %s for client %s from catalog.\n"), del.num_del,
405          del.num_del==1?_("Job"):_("Jobs"), client->name());
406     } else if (ua->verbose) {
407        ua->info_msg(_("No Jobs found to prune.\n"));
408     }
409
410 bail_out:
411    drop_temp_tables(ua);
412    db_unlock(ua->db);
413    if (del.JobId) {
414       free(del.JobId);
415    }
416    if (del.PurgedFiles) {
417       free(del.PurgedFiles);
418    }
419    return 1;
420 }
421
422 /*
423  * Prune a given Volume
424  */
425 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
426 {
427    POOL_MEM query(PM_MESSAGE);
428    struct del_ctx del;
429    bool ok = false;
430    int count;
431
432    if (mr->Enabled == 2) {
433       return false;                   /* Cannot prune archived volumes */
434    }
435
436    memset(&del, 0, sizeof(del));
437    del.max_ids = 10000;
438    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
439
440    db_lock(ua->db);
441
442    /* Prune only Volumes with status "Full", or "Used" */
443    if (strcmp(mr->VolStatus, "Full")   == 0 ||
444        strcmp(mr->VolStatus, "Used")   == 0) {
445       Dmsg2(050, "get prune list MediaId=%d Volume %s\n", (int)mr->MediaId, mr->VolumeName);
446       count = get_prune_list_for_volume(ua, mr, &del);
447       Dmsg1(050, "Num pruned = %d\n", count);
448       if (count != 0) {
449          purge_job_list_from_catalog(ua, del);
450       }
451       ok = is_volume_purged(ua, mr);
452    }
453
454    db_unlock(ua->db);
455    if (del.JobId) {
456       free(del.JobId);
457    }
458    return ok;
459 }
460
461 /*
462  * Get prune list for a volume
463  */
464 int get_prune_list_for_volume(UAContext *ua, MEDIA_DBR *mr, del_ctx *del)
465 {
466    POOL_MEM query(PM_MESSAGE);
467    int count = 0;
468    int i;          
469    utime_t now, period;
470    char ed1[50], ed2[50];
471    JCR *jcr;
472    bool skip;
473
474    if (mr->Enabled == 2) {
475       return 0;                    /* cannot prune Archived volumes */
476    }
477
478    db_lock(ua->db);
479
480    /*
481     * Now add to the  list of JobIds for Jobs written to this Volume
482     */
483    edit_int64(mr->MediaId, ed1); 
484    period = mr->VolRetention;
485    now = (utime_t)time(NULL);
486    edit_uint64(now-period, ed2);
487    Mmsg(query, sel_JobMedia, ed1, ed2);
488    Dmsg3(250, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
489       (int)(now-period));
490
491    Dmsg1(050, "Query=%s\n", query.c_str());
492    if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)del)) {
493       if (ua->verbose) {
494          ua->error_msg("%s", db_strerror(ua->db));
495       }
496       Dmsg0(050, "Count failed\n");
497       goto bail_out;
498    }
499
500    /* Do not prune any job currently running */
501    for (i=0; i < del->num_ids; i++) {
502       skip = false;
503       foreach_jcr(jcr) {
504          if (jcr->JobId == del->JobId[i]) {
505             Dmsg2(150, "skip same job JobId[%d]=%d\n", i, (int)del->JobId[i]);
506             del->JobId[i] = 0;
507             skip = true;
508             break;
509          }
510       }
511       if (skip) {
512          continue;
513       }
514       Dmsg2(150, "accept JobId[%d]=%d\n", i, (int)del->JobId[i]);
515       count++;
516    }
517
518 bail_out:
519    db_unlock(ua->db);
520    return count;
521 }