]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_prune.c
Apply patch (with some difficulties) from Joao Henrique Freitas
[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 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 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  * 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  */
111 int prunecmd(UAContext *ua, const char *cmd)
112 {
113    CLIENT *client;
114    POOL_DBR pr;
115    MEDIA_DBR mr;
116    int kw;
117
118    static const char *keywords[] = {
119       NT_("Files"),
120       NT_("Jobs"),
121       NT_("Volume"),
122       NULL};
123
124    if (!open_client_db(ua)) {
125       return false;
126    }
127
128    /* First search args */
129    kw = find_arg_keyword(ua, keywords);
130    if (kw < 0 || kw > 2) {
131       /* no args, so ask user */
132       kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);
133    }
134
135    switch (kw) {
136    case 0:  /* prune files */
137       client = get_client_resource(ua);
138       if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
139          return false;
140       }
141       prune_files(ua, client);
142       return true;
143    case 1:  /* prune jobs */
144       client = get_client_resource(ua);
145       if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
146          return false;
147       }
148       /* ****FIXME**** allow user to select JobType */
149       prune_jobs(ua, client, JT_BACKUP);
150       return 1;
151    case 2:  /* prune volume */
152       if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
153          return false;
154       }
155       if (mr.Enabled == 2) {
156          ua->error_msg(_("Cannot prune Volume \"%s\" because it is archived.\n"),
157             mr.VolumeName);
158          return false;
159       }
160       if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
161          return false;
162       }
163       prune_volume(ua, &mr);
164       return true;
165    default:
166       break;
167    }
168
169    return true;
170 }
171
172 /*
173  * Prune File records from the database. For any Job which
174  * is older than the retention period, we unconditionally delete
175  * all File records for that Job.  This is simple enough that no
176  * temporary tables are needed. We simply make an in memory list of
177  * the JobIds meeting the prune conditions, then delete all File records
178  * pointing to each of those JobIds.
179  *
180  * This routine assumes you want the pruning to be done. All checking
181  *  must be done before calling this routine.
182  */
183 int prune_files(UAContext *ua, CLIENT *client)
184 {
185    struct del_ctx del;
186    struct s_count_ctx cnt;
187    POOL_MEM query(PM_MESSAGE);
188    utime_t now, period;
189    CLIENT_DBR cr;
190    char ed1[50], ed2[50];
191
192    db_lock(ua->db);
193    memset(&cr, 0, sizeof(cr));
194    memset(&del, 0, sizeof(del));
195    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
196    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
197       db_unlock(ua->db);
198       return 0;
199    }
200
201    period = client->FileRetention;
202    now = (utime_t)time(NULL);
203
204    /* Select Jobs -- for counting */
205    Mmsg(query, count_select_job, edit_uint64(now - period, ed1), 
206         edit_int64(cr.ClientId, ed2));
207    Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, 
208                (uint32_t)period, query.c_str());
209    cnt.count = 0;
210    if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
211       ua->error_msg("%s", db_strerror(ua->db));
212       Dmsg0(050, "Count failed\n");
213       goto bail_out;
214    }
215
216    if (cnt.count == 0) {
217       if (ua->verbose) {
218          ua->warning_msg(_("No Files found to prune.\n"));
219       }
220       goto bail_out;
221    }
222
223    if (cnt.count < MAX_DEL_LIST_LEN) {
224       del.max_ids = cnt.count + 1;
225    } else {
226       del.max_ids = MAX_DEL_LIST_LEN;
227    }
228    del.tot_ids = 0;
229
230    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
231
232    /* Now process same set but making a delete list */
233    Mmsg(query, select_job, edit_uint64(now - period, ed1), 
234         edit_int64(cr.ClientId, ed2));
235    db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del);
236
237    purge_files_from_job_list(ua, del);
238
239    edit_uint64_with_commas(del.num_del, ed1);
240    ua->info_msg(_("Pruned Files from %s Jobs for client %s from catalog.\n"),
241       ed1, client->name());
242
243 bail_out:
244    db_unlock(ua->db);
245    if (del.JobId) {
246       free(del.JobId);
247    }
248    return 1;
249 }
250
251
252 static void drop_temp_tables(UAContext *ua)
253 {
254    int i;
255    for (i=0; drop_deltabs[i]; i++) {
256       db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
257    }
258 }
259
260 static bool create_temp_tables(UAContext *ua)
261 {
262    /* Create temp tables and indicies */
263    if (!db_sql_query(ua->db, create_deltabs[db_type], NULL, (void *)NULL)) {
264       ua->error_msg("%s", db_strerror(ua->db));
265       Dmsg0(050, "create DelTables table failed\n");
266       return false;
267    }
268    if (!db_sql_query(ua->db, create_delindex, NULL, (void *)NULL)) {
269        ua->error_msg("%s", db_strerror(ua->db));
270        Dmsg0(050, "create DelInx1 index failed\n");
271        return false;
272    }
273    return true;
274 }
275
276
277
278 /*
279  * Pruning Jobs is a bit more complicated than purging Files
280  * because we delete Job records only if there is a more current
281  * backup of the FileSet. Otherwise, we keep the Job record.
282  * In other words, we never delete the only Job record that
283  * contains a current backup of a FileSet. This prevents the
284  * Volume from being recycled and destroying a current backup.
285  *
286  * For Verify Jobs, we do not delete the last InitCatalog.
287  *
288  * For Restore Jobs there are no restrictions.
289  */
290 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
291 {
292    struct del_ctx del;
293    POOL_MEM query(PM_MESSAGE);
294    utime_t now, period;
295    CLIENT_DBR cr;
296    char ed1[50], ed2[50];
297
298    db_lock(ua->db);
299    memset(&cr, 0, sizeof(cr));
300    memset(&del, 0, sizeof(del));
301
302    bstrncpy(cr.Name, client->name(), sizeof(cr.Name));
303    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
304       db_unlock(ua->db);
305       return 0;
306    }
307
308    period = client->JobRetention;
309    now = (utime_t)time(NULL);
310
311    /* Drop any previous temporary tables still there */
312    drop_temp_tables(ua);
313
314    /* Create temp tables and indicies */
315    if (!create_temp_tables(ua)) {
316       goto bail_out;
317    }
318
319    /*
320     * Select all files that are older than the JobRetention period
321     *  and stuff them into the "DeletionCandidates" table.
322     */
323    edit_uint64(now - period, ed1);
324    Mmsg(query, insert_delcand, (char)JobType, ed1, 
325         edit_int64(cr.ClientId, ed2));
326    if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) {
327       if (ua->verbose) {
328          ua->error_msg("%s", db_strerror(ua->db));
329       }
330       Dmsg0(050, "insert delcand failed\n");
331       goto bail_out;
332    }
333
334    del.max_ids = 100;
335    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
336    del.PurgedFiles = (char *)malloc(del.max_ids);
337
338    /* ed1 = JobTDate */
339    edit_int64(cr.ClientId, ed2);
340    switch (JobType) {
341    case JT_BACKUP:
342       Mmsg(query, select_backup_del, ed1, ed2);
343       break;
344    case JT_RESTORE:
345       Mmsg(query, select_restore_del, ed1, ed2);
346       break;
347    case JT_VERIFY:
348       Mmsg(query, select_verify_del, ed1, ed2);
349       break;
350    case JT_ADMIN:
351       Mmsg(query, select_admin_del, ed1, ed2);
352       break;
353    case JT_MIGRATE:
354       Mmsg(query, select_migrate_del, ed1, ed2);
355       break;
356    }
357
358    Dmsg1(150, "Query=%s\n", query.c_str());
359    if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
360       ua->error_msg("%s", db_strerror(ua->db));
361    }
362
363    purge_job_list_from_catalog(ua, del);
364
365    if (del.num_del > 0) {
366       ua->info_msg(_("Pruned %d %s for client %s from catalog.\n"), del.num_del,
367          del.num_del==1?_("Job"):_("Jobs"), client->name());
368     } else if (ua->verbose) {
369        ua->info_msg(_("No Jobs found to prune.\n"));
370     }
371
372 bail_out:
373    drop_temp_tables(ua);
374    db_unlock(ua->db);
375    if (del.JobId) {
376       free(del.JobId);
377    }
378    if (del.PurgedFiles) {
379       free(del.PurgedFiles);
380    }
381    return 1;
382 }
383
384 /*
385  * Prune a given Volume
386  */
387 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
388 {
389    POOL_MEM query(PM_MESSAGE);
390    struct del_ctx del;
391    bool ok = false;
392    int count;
393
394    if (mr->Enabled == 2) {
395       return false;                   /* Cannot prune archived volumes */
396    }
397
398    memset(&del, 0, sizeof(del));
399    del.max_ids = 10000;
400    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
401
402    db_lock(ua->db);
403
404    /* Prune only Volumes with status "Full", or "Used" */
405    if (strcmp(mr->VolStatus, "Full")   == 0 ||
406        strcmp(mr->VolStatus, "Used")   == 0) {
407       Dmsg2(050, "get prune list MediaId=%d Volume %s\n", (int)mr->MediaId, mr->VolumeName);
408       count = get_prune_list_for_volume(ua, mr, &del);
409       Dmsg1(050, "Num pruned = %d\n", count);
410       if (count != 0) {
411          purge_job_list_from_catalog(ua, del);
412       }
413       ok = is_volume_purged(ua, mr);
414    }
415
416    db_unlock(ua->db);
417    if (del.JobId) {
418       free(del.JobId);
419    }
420    return ok;
421 }
422
423 /*
424  * Get prune list for a volume
425  */
426 int get_prune_list_for_volume(UAContext *ua, MEDIA_DBR *mr, del_ctx *del)
427 {
428    POOL_MEM query(PM_MESSAGE);
429    int count = 0;
430    int i;          
431    utime_t now, period;
432    char ed1[50], ed2[50];
433
434    if (mr->Enabled == 2) {
435       return 0;                    /* cannot prune Archived volumes */
436    }
437
438    db_lock(ua->db);
439
440    /*
441     * Now add to the  list of JobIds for Jobs written to this Volume
442     */
443    edit_int64(mr->MediaId, ed1); 
444    period = mr->VolRetention;
445    now = (utime_t)time(NULL);
446    edit_uint64(now-period, ed2);
447    Mmsg(query, sel_JobMedia, ed1, ed2);
448    Dmsg3(250, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
449       (int)(now-period));
450
451    Dmsg1(050, "Query=%s\n", query.c_str());
452    if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)del)) {
453       if (ua->verbose) {
454          ua->error_msg("%s", db_strerror(ua->db));
455       }
456       Dmsg0(050, "Count failed\n");
457       goto bail_out;
458    }
459
460    for (i=0; i < del->num_ids; i++) {
461       if (ua->jcr->JobId == del->JobId[i]) {
462          Dmsg2(150, "skip same job JobId[%d]=%d\n", i, (int)del->JobId[i]);
463          del->JobId[i] = 0;
464          continue;
465       }
466       Dmsg2(150, "accept JobId[%d]=%d\n", i, (int)del->JobId[i]);
467       count++;
468    }
469
470 bail_out:
471    db_unlock(ua->db);
472    return count;
473 }